Codec for Location.

* Moves SingletonCodec to third_party.

PiperOrigin-RevId: 182143153
diff --git a/src/main/java/com/google/devtools/build/lib/BUILD b/src/main/java/com/google/devtools/build/lib/BUILD
index a9c52a3..826ce84 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 @@
     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 @@
         "//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 24d2c2d..412d601 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 @@
 /**
  * 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 @@
     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 @@
     }
   }
 
-  /**
-   * 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 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 0000000..3dbf328
--- /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 10ff03e..7e3b731 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.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 @@
   }
 
   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 @@
       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 1e20e45..4706fa2 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 @@
  * 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 @@
     // 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 @@
     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];
+      this(computeLinestart(buffer), path, buffer.length);
+    }
 
-      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;
-        }
-      }
-      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 @@
           && 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
-     */
-    private static class SingleHashLine implements Serializable {
+    /** Represents a "#line" directive */
+    @AutoCodec
+    static class SingleHashLine implements Serializable {
+      public static final ObjectCodec<SingleHashLine> CODEC =
+          new LineNumberTable_HashLine_SingleHashLine_AutoCodec();
+
       private final int offset;
       private final int line;
       private final PathFragment path;
@@ -191,6 +209,23 @@
         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 @@
 
     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 @@
                 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) {