diff options
author | 2018-01-16 18:21:16 -0800 | |
---|---|---|
committer | 2018-01-16 18:22:48 -0800 | |
commit | 7ac7b63c658509fd335db6f0149da8e2786c488a (patch) | |
tree | 04bf7c852fc0137ace91ce568c7a4a268396ed79 /src/main/java | |
parent | 6f95124c3453006149eac955d7620540c7d6bda4 (diff) |
Codec for Location.
* Moves SingletonCodec to third_party.
PiperOrigin-RevId: 182143153
Diffstat (limited to 'src/main/java')
5 files changed, 232 insertions, 66 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD index a9c52a334a..826ce84a35 100644 --- a/src/main/java/com/google/devtools/build/lib/BUILD +++ b/src/main/java/com/google/devtools/build/lib/BUILD @@ -240,6 +240,7 @@ java_library( deps = [ "//src/main/java/com/google/devtools/build/lib:io", "//src/main/java/com/google/devtools/build/lib/concurrent", + "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec", "//src/main/java/com/google/devtools/build/lib/vfs", "//third_party:guava", "//third_party:jsr305", @@ -327,6 +328,7 @@ java_library( "//src/main/java/com/google/devtools/build/lib/collect/nestedset", "//src/main/java/com/google/devtools/build/lib/concurrent", "//src/main/java/com/google/devtools/build/lib/profiler", + "//src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec", "//src/main/java/com/google/devtools/build/lib/vfs:pathfragment", "//third_party:asm", "//third_party:asm-commons", diff --git a/src/main/java/com/google/devtools/build/lib/events/Location.java b/src/main/java/com/google/devtools/build/lib/events/Location.java index 24d2c2dcb8..412d601a2d 100644 --- a/src/main/java/com/google/devtools/build/lib/events/Location.java +++ b/src/main/java/com/google/devtools/build/lib/events/Location.java @@ -15,6 +15,9 @@ package com.google.devtools.build.lib.events; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; +import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; +import com.google.devtools.build.lib.skyframe.serialization.SingletonCodec; +import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import java.io.Serializable; @@ -23,24 +26,29 @@ import java.util.Objects; /** * A Location is a range of characters within a file. * - * <p>The start and end locations may be the same, in which case the Location - * denotes a point in the file, not a range. The path may be null, indicating - * an unknown file. + * <p>The start and end locations may be the same, in which case the Location denotes a point in the + * file, not a range. The path may be null, indicating an unknown file. * - * <p>Implementations of Location should be optimised for speed of construction, - * not speed of attribute access, as far more Locations are created during - * parsing than are ever used to display error messages. + * <p>Implementations of Location should be optimised for speed of construction, not speed of + * attribute access, as far more Locations are created during parsing than are ever used to display + * error messages. */ +@AutoCodec(strategy = AutoCodec.Strategy.POLYMORPHIC) public abstract class Location implements Serializable { + public static final ObjectCodec<Location> CODEC = new Location_AutoCodec(); + @AutoCodec @Immutable - private static final class LocationWithPathAndStartColumn extends Location { + static final class LocationWithPathAndStartColumn extends Location { + public static final ObjectCodec<LocationWithPathAndStartColumn> CODEC = + new Location_LocationWithPathAndStartColumn_AutoCodec(); + private final PathFragment path; private final LineAndColumn startLineAndColumn; - private LocationWithPathAndStartColumn(PathFragment path, int startOffSet, int endOffSet, - LineAndColumn startLineAndColumn) { - super(startOffSet, endOffSet); + LocationWithPathAndStartColumn( + PathFragment path, int startOffset, int endOffset, LineAndColumn startLineAndColumn) { + super(startOffset, endOffset); this.path = path; this.startLineAndColumn = startLineAndColumn; } @@ -254,11 +262,12 @@ public abstract class Location implements Serializable { return this.startOffset == that.startOffset && this.endOffset == that.endOffset; } - /** - * A value class that describes the line and column of an offset in a file. - */ + /** A value class that describes the line and column of an offset in a file. */ + @AutoCodec @Immutable - public static final class LineAndColumn implements Serializable { + public static final class LineAndColumn { + public static final ObjectCodec<LineAndColumn> CODEC = new Location_LineAndColumn_AutoCodec(); + private final int line; private final int column; @@ -293,11 +302,16 @@ public abstract class Location implements Serializable { } } - /** - * Dummy location for built-in functions which ensures that stack traces contain "nice" location - * strings. - */ - public static final Location BUILTIN = new Location(0, 0) { + private static final class BuiltinLocation extends Location { + public static final BuiltinLocation INSTANCE = new BuiltinLocation(); + + public static final ObjectCodec<BuiltinLocation> CODEC = + SingletonCodec.of(INSTANCE, "BuiltinLocation"); + + private BuiltinLocation() { + super(0, 0); + } + @Override public String toString() { return "Built-In"; @@ -307,7 +321,23 @@ public abstract class Location implements Serializable { public PathFragment getPath() { return null; } - }; + + @Override + public int hashCode() { + return internalHashCode(); + } + + @Override + public boolean equals(Object object) { + return object instanceof BuiltinLocation; + } + } + + /** + * Dummy location for built-in functions which ensures that stack traces contain "nice" location + * strings. + */ + public static final Location BUILTIN = BuiltinLocation.INSTANCE; /** * Returns the location in the format "filename:line". diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SingletonCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SingletonCodec.java new file mode 100644 index 0000000000..3dbf328f37 --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SingletonCodec.java @@ -0,0 +1,88 @@ +// Copyright 2018 The Bazel Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.skyframe.serialization; + +import com.google.common.base.Preconditions; +import com.google.protobuf.CodedInputStream; +import com.google.protobuf.CodedOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +/** + * Specialized {@link ObjectCodec} for storing singleton values. Values serialize to a supplied + * representation, which is useful for debugging and is used to verify the serialized representation + * during deserialization. + */ +public class SingletonCodec<T> implements ObjectCodec<T> { + + /** + * Create instance wrapping the singleton {@code value}. Will serialize to the byte array + * representation of {@code mnemonic}. On deserialization if {@code mnemonic} matches the + * serialized data then {@code value} is returned. + */ + public static <T> SingletonCodec<T> of(T value, String mnemonic) { + return new SingletonCodec<T>(value, mnemonic); + } + + private final T value; + private final byte[] mnemonic; + + private SingletonCodec(T value, String mnemonic) { + this.value = Preconditions.checkNotNull(value, "SingletonCodec cannot represent null"); + this.mnemonic = mnemonic.getBytes(StandardCharsets.UTF_8); + } + + @SuppressWarnings("unchecked") + @Override + public Class<T> getEncodedClass() { + return (Class<T>) value.getClass(); + } + + @Override + public void serialize(T t, CodedOutputStream codedOut) throws IOException { + // TODO(michajlo): See how usefuly mnemonic actually winds up being for debugging, we may + // want to just toss it and trust that the classifier for this value is good enough. + codedOut.writeByteArrayNoTag(mnemonic); + } + + @Override + public T deserialize(CodedInputStream codedIn) throws SerializationException, IOException { + // Get ByteBuffer instead of raw bytes, as it may be a direct view of the data and not a copy, + // which is much more efficient. + ByteBuffer readMnemonic = codedIn.readByteBuffer(); + if (!bytesEqual(mnemonic, readMnemonic)) { + throw new SerializationException( + "Failed to decode singleton " + value + " expected " + Arrays.toString(mnemonic)); + } + return value; + } + + private static boolean bytesEqual(byte[] expected, ByteBuffer buffer) { + if (buffer.remaining() != expected.length) { + return false; + } + + for (int i = 0; i < expected.length; i++) { + if (expected[i] != buffer.get(i)) { + return false; + } + } + + return true; + } +} + diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java index 10ff03e100..7e3b731d6f 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/Lexer.java @@ -23,6 +23,8 @@ import com.google.devtools.build.lib.events.EventHandler; import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.profiler.ProfilerTask; +import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; +import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.vfs.PathFragment; import java.util.HashMap; @@ -157,19 +159,21 @@ public final class Lexer { } Location createLocation(int start, int end) { - return new LexerLocation(locationInfo, start, end); + return new LexerLocation(locationInfo.lineNumberTable, start, end); } // Don't use an inner class as we don't want to close over the Lexer, only // the LocationInfo. + @AutoCodec @Immutable - private static final class LexerLocation extends Location { + static final class LexerLocation extends Location { + public static final ObjectCodec<LexerLocation> CODEC = new Lexer_LexerLocation_AutoCodec(); private final LineNumberTable lineNumberTable; - LexerLocation(LocationInfo locationInfo, int start, int end) { - super(start, end); - this.lineNumberTable = locationInfo.lineNumberTable; + LexerLocation(LineNumberTable lineNumberTable, int startOffset, int endOffset) { + super(startOffset, endOffset); + this.lineNumberTable = lineNumberTable; } @Override @@ -192,7 +196,6 @@ public final class Lexer { return lineNumberTable.getLineAndColumn(endOffset); } - @Override public int hashCode() { return Objects.hash(lineNumberTable, internalHashCode()); diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java b/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java index 1e20e4577a..4706fa2f3b 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/LineNumberTable.java @@ -15,9 +15,12 @@ package com.google.devtools.build.lib.syntax; import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.events.Location.LineAndColumn; +import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; +import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.build.lib.util.StringUtilities; import com.google.devtools.build.lib.vfs.PathFragment; @@ -38,7 +41,9 @@ import java.util.regex.Pattern; * their buffer using {@link #create}. The client can then ask for the line and column given a * position using ({@link #getLineAndColumn(int)}). */ +@AutoCodec(strategy = AutoCodec.Strategy.POLYMORPHIC) public abstract class LineNumberTable implements Serializable { + public static final ObjectCodec<LineNumberTable> CODEC = new LineNumberTable_AutoCodec(); /** * Returns the (line, column) pair for the specified offset. @@ -61,16 +66,15 @@ public abstract class LineNumberTable implements Serializable { // by gconfig2blaze. We ignore all actual newlines and compute the logical // LNT based only on the presence of #line markers. return StringUtilities.containsSubarray(buffer, "\n#line ".toCharArray()) - ? new HashLine(buffer, path) + ? HashLine.create(buffer, path) : new Regular(buffer, path); } - /** - * Line number table implementation for regular source files. Records - * offsets of newlines. - */ + /** Line number table implementation for regular source files. Records offsets of newlines. */ + @AutoCodec @Immutable public static class Regular extends LineNumberTable { + public static final ObjectCodec<Regular> CODEC = new LineNumberTable_Regular_AutoCodec(); /** * A mapping from line number (line >= 1) to character offset into the file. @@ -80,32 +84,14 @@ public abstract class LineNumberTable implements Serializable { private final int bufferLength; public Regular(char[] buffer, PathFragment path) { - // Compute the size. - int size = 2; - for (int i = 0; i < buffer.length; i++) { - if (buffer[i] == '\n') { - size++; - } - } - linestart = new int[size]; - - int index = 0; - linestart[index++] = 0; // The 0th line does not exist - so we fill something in - // to make sure the start pos for the 1st line ends up at - // linestart[1]. Using 0 is useful for tables that are - // completely empty. - linestart[index++] = 0; // The first line ("line 1") starts at offset 0. + this(computeLinestart(buffer), path, buffer.length); + } - // Scan the buffer and record the offset of each line start. Doing this - // once upfront is faster than checking each char as it is pulled from - // the buffer. - for (int i = 0; i < buffer.length; i++) { - if (buffer[i] == '\n') { - linestart[index++] = i + 1; - } - } - this.bufferLength = buffer.length; + @AutoCodec.Constructor + Regular(int[] linestart, PathFragment path, int bufferLength) { + this.linestart = linestart; this.path = path; + this.bufferLength = bufferLength; } private int getLineAt(int offset) { @@ -169,19 +155,51 @@ public abstract class LineNumberTable implements Serializable { && Arrays.equals(this.linestart, that.linestart) && Objects.equals(this.path, that.path); } + + private static int[] computeLinestart(char[] buffer) { + // Compute the size. + int size = 2; + for (int i = 0; i < buffer.length; i++) { + if (buffer[i] == '\n') { + size++; + } + } + int[] linestart = new int[size]; + + int index = 0; + linestart[index++] = 0; // The 0th line does not exist - so we fill something in + // to make sure the start pos for the 1st line ends up at + // linestart[1]. Using 0 is useful for tables that are + // completely empty. + linestart[index++] = 0; // The first line ("line 1") starts at offset 0. + + // Scan the buffer and record the offset of each line start. Doing this + // once upfront is faster than checking each char as it is pulled from + // the buffer. + for (int i = 0; i < buffer.length; i++) { + if (buffer[i] == '\n') { + linestart[index++] = i + 1; + } + } + return linestart; + } } /** - * Line number table implementation for source files that have been - * preprocessed. Ignores newlines and uses only #line directives. + * Line number table implementation for source files that have been preprocessed. Ignores newlines + * and uses only #line directives. */ + @AutoCodec @Immutable public static class HashLine extends LineNumberTable { + public static final ObjectCodec<HashLine> CODEC = new LineNumberTable_HashLine_AutoCodec(); + + /** Represents a "#line" directive */ + @AutoCodec + static class SingleHashLine implements Serializable { + public static final ObjectCodec<SingleHashLine> CODEC = + new LineNumberTable_HashLine_SingleHashLine_AutoCodec(); - /** - * Represents a "#line" directive - */ - private static class SingleHashLine implements Serializable { private final int offset; private final int line; private final PathFragment path; @@ -191,6 +209,23 @@ public abstract class LineNumberTable implements Serializable { this.line = line; this.path = path; } + + @Override + public int hashCode() { + return Objects.hash(offset, line, path); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof SingleHashLine)) { + return false; + } + SingleHashLine that = (SingleHashLine) obj; + return offset == that.offset && line == that.line && Objects.equals(path, that.path); + } } private static final Ordering<SingleHashLine> hashOrdering = @@ -204,11 +239,11 @@ public abstract class LineNumberTable implements Serializable { private static final Pattern pattern = Pattern.compile("\n#line ([0-9]+) \"([^\"\\n]+)\""); - private final List<SingleHashLine> table; + private final ImmutableList<SingleHashLine> table; private final PathFragment defaultPath; private final int bufferLength; - public HashLine(char[] buffer, PathFragment defaultPath) { + public static HashLine create(char[] buffer, PathFragment defaultPath) { CharSequence bufString = CharBuffer.wrap(buffer); Matcher m = pattern.matcher(bufString); List<SingleHashLine> unorderedTable = new ArrayList<>(); @@ -221,9 +256,17 @@ public abstract class LineNumberTable implements Serializable { Integer.parseInt(m.group(1)), // line number pathFragment)); // filename is an absolute path } - this.table = hashOrdering.immutableSortedCopy(unorderedTable); - this.bufferLength = buffer.length; - this.defaultPath = Preconditions.checkNotNull(defaultPath); + return new HashLine( + hashOrdering.immutableSortedCopy(unorderedTable), + Preconditions.checkNotNull(defaultPath), + buffer.length); + } + + @AutoCodec.Constructor + HashLine(ImmutableList<SingleHashLine> table, PathFragment defaultPath, int bufferLength) { + this.table = table; + this.defaultPath = defaultPath; + this.bufferLength = bufferLength; } private SingleHashLine getHashLine(int offset) { |