• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2022 Uber Technologies, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 
23 package com.uber.nullaway.fixserialization;
24 
25 import com.sun.tools.javac.code.Symbol;
26 import com.uber.nullaway.ErrorMessage;
27 import com.uber.nullaway.fixserialization.adapters.SerializationAdapter;
28 import com.uber.nullaway.fixserialization.out.ErrorInfo;
29 import com.uber.nullaway.fixserialization.out.FieldInitializationInfo;
30 import com.uber.nullaway.fixserialization.out.SuggestedNullableFixInfo;
31 import java.io.FileOutputStream;
32 import java.io.IOException;
33 import java.io.OutputStream;
34 import java.io.Writer;
35 import java.net.URI;
36 import java.nio.charset.Charset;
37 import java.nio.file.Files;
38 import java.nio.file.Path;
39 import java.nio.file.Paths;
40 import javax.annotation.Nullable;
41 
42 /**
43  * Serializer class where all generated files in Fix Serialization package is created through APIs
44  * of this class.
45  */
46 public class Serializer {
47   /** Path to write errors. */
48   private final Path errorOutputPath;
49 
50   /** Path to write suggested fix metadata. */
51   private final Path suggestedFixesOutputPath;
52 
53   /** Path to write suggested fix metadata. */
54   private final Path fieldInitializationOutputPath;
55 
56   /**
57    * Adapter used to serialize outputs. This adapter is capable of serializing outputs according to
58    * the requested serilization version and maintaining backward compatibility with previous
59    * versions of NullAway.
60    */
61   private final SerializationAdapter serializationAdapter;
62 
Serializer(FixSerializationConfig config, SerializationAdapter serializationAdapter)63   public Serializer(FixSerializationConfig config, SerializationAdapter serializationAdapter) {
64     String outputDirectory = config.outputDirectory;
65     this.errorOutputPath = Paths.get(outputDirectory, "errors.tsv");
66     this.suggestedFixesOutputPath = Paths.get(outputDirectory, "fixes.tsv");
67     this.fieldInitializationOutputPath = Paths.get(outputDirectory, "field_init.tsv");
68     this.serializationAdapter = serializationAdapter;
69     serializeVersion(outputDirectory);
70     initializeOutputFiles(config);
71   }
72 
73   /**
74    * Appends the string representation of the {@link SuggestedNullableFixInfo}.
75    *
76    * @param suggestedNullableFixInfo SuggestedFixInfo object.
77    * @param enclosing Flag to control if enclosing method and class should be included.
78    */
serializeSuggestedFixInfo( SuggestedNullableFixInfo suggestedNullableFixInfo, boolean enclosing)79   public void serializeSuggestedFixInfo(
80       SuggestedNullableFixInfo suggestedNullableFixInfo, boolean enclosing) {
81     if (enclosing) {
82       suggestedNullableFixInfo.initEnclosing();
83     }
84     appendToFile(
85         suggestedNullableFixInfo.tabSeparatedToString(serializationAdapter),
86         suggestedFixesOutputPath);
87   }
88 
89   /**
90    * Appends the string representation of the {@link ErrorMessage}.
91    *
92    * @param errorInfo ErrorMessage object.
93    */
serializeErrorInfo(ErrorInfo errorInfo)94   public void serializeErrorInfo(ErrorInfo errorInfo) {
95     errorInfo.initEnclosing();
96     appendToFile(serializationAdapter.serializeError(errorInfo), errorOutputPath);
97   }
98 
serializeFieldInitializationInfo(FieldInitializationInfo info)99   public void serializeFieldInitializationInfo(FieldInitializationInfo info) {
100     appendToFile(info.tabSeparatedToString(serializationAdapter), fieldInitializationOutputPath);
101   }
102 
103   /** Cleared the content of the file if exists and writes the header in the first line. */
initializeFile(Path path, String header)104   private void initializeFile(Path path, String header) {
105     try {
106       Files.deleteIfExists(path);
107     } catch (IOException e) {
108       throw new RuntimeException("Could not clear file at: " + path, e);
109     }
110     try (OutputStream os = new FileOutputStream(path.toFile())) {
111       header += "\n";
112       os.write(header.getBytes(Charset.defaultCharset()), 0, header.length());
113       os.flush();
114     } catch (IOException e) {
115       throw new RuntimeException("Could not finish resetting File at Path: " + path, e);
116     }
117   }
118 
119   /**
120    * Returns the serialization version.
121    *
122    * @return The serialization version.
123    */
getSerializationVersion()124   public int getSerializationVersion() {
125     return serializationAdapter.getSerializationVersion();
126   }
127 
128   /**
129    * Serializes the using {@link SerializationAdapter} version as {@code string} in
130    * <b>serialization_version.txt</b> file under root output directory for all serialized outputs.
131    *
132    * @param outputDirectory Path to root directory for all serialized outputs.
133    */
serializeVersion(@ullable String outputDirectory)134   private void serializeVersion(@Nullable String outputDirectory) {
135     Path versionOutputPath = Paths.get(outputDirectory).resolve("serialization_version.txt");
136     try (Writer fileWriter =
137         Files.newBufferedWriter(versionOutputPath.toFile().toPath(), Charset.defaultCharset())) {
138       fileWriter.write(Integer.toString(serializationAdapter.getSerializationVersion()));
139     } catch (IOException exception) {
140       throw new RuntimeException("Could not serialize output version", exception);
141     }
142   }
143 
144   /** Initializes every file which will be re-generated in the new run of NullAway. */
initializeOutputFiles(FixSerializationConfig config)145   private void initializeOutputFiles(FixSerializationConfig config) {
146     try {
147       Files.createDirectories(Paths.get(config.outputDirectory));
148       if (config.suggestEnabled) {
149         initializeFile(suggestedFixesOutputPath, SuggestedNullableFixInfo.header());
150       }
151       if (config.fieldInitInfoEnabled) {
152         initializeFile(fieldInitializationOutputPath, FieldInitializationInfo.header());
153       }
154       initializeFile(errorOutputPath, serializationAdapter.getErrorsOutputFileHeader());
155     } catch (IOException e) {
156       throw new RuntimeException("Could not finish resetting serializer", e);
157     }
158   }
159 
appendToFile(String row, Path path)160   private void appendToFile(String row, Path path) {
161     // Since there is no method available in API of either javac or errorprone to inform NullAway
162     // that the analysis is finished, we cannot open a single stream and flush it within a finalize
163     // method. Must open and close a new stream everytime we are appending a new line to a file.
164     if (row == null || row.equals("")) {
165       return;
166     }
167     row = row + "\n";
168     try (OutputStream os = new FileOutputStream(path.toFile(), true)) {
169       os.write(row.getBytes(Charset.defaultCharset()), 0, row.length());
170       os.flush();
171     } catch (IOException e) {
172       throw new RuntimeException("Error happened for writing at file: " + path, e);
173     }
174   }
175 
176   /**
177    * Converts the given uri to the real path. Note, in NullAway CI tests, source files exists in
178    * memory and there is no real path leading to those files. Instead, we just serialize the path
179    * from uri as the full paths are not checked in tests.
180    *
181    * @param uri Given uri.
182    * @return Real path for the give uri.
183    */
184   @Nullable
pathToSourceFileFromURI(@ullable URI uri)185   public static Path pathToSourceFileFromURI(@Nullable URI uri) {
186     if (uri == null) {
187       return null;
188     }
189     if ("jimfs".equals(uri.getScheme())) {
190       // In NullAway unit tests, files are stored in memory and have this scheme.
191       return Paths.get(uri);
192     }
193     if (!"file".equals(uri.getScheme())) {
194       return null;
195     }
196     Path path = Paths.get(uri);
197     try {
198       return path.toRealPath();
199     } catch (IOException e) {
200       // In this case, we still would like to continue the serialization instead of returning null
201       // and not serializing anything.
202       return path;
203     }
204   }
205 
206   /**
207    * Serializes the given {@link Symbol} to a string.
208    *
209    * @param symbol The symbol to serialize.
210    * @param adapter adapter used to serialize symbols.
211    * @return The serialized symbol.
212    */
serializeSymbol(@ullable Symbol symbol, SerializationAdapter adapter)213   public static String serializeSymbol(@Nullable Symbol symbol, SerializationAdapter adapter) {
214     if (symbol == null) {
215       return "null";
216     }
217     switch (symbol.getKind()) {
218       case FIELD:
219       case PARAMETER:
220         return symbol.name.toString();
221       case METHOD:
222       case CONSTRUCTOR:
223         return adapter.serializeMethodSignature((Symbol.MethodSymbol) symbol);
224       default:
225         return symbol.flatName().toString();
226     }
227   }
228 }
229