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.google.common.base.Preconditions; 26 import com.google.common.collect.ImmutableMap; 27 import com.google.errorprone.VisitorState; 28 import com.sun.source.tree.Tree; 29 import com.sun.source.util.TreePath; 30 import com.sun.source.util.Trees; 31 import com.sun.tools.javac.code.Symbol; 32 import com.sun.tools.javac.processing.JavacProcessingEnvironment; 33 import com.uber.nullaway.Config; 34 import com.uber.nullaway.ErrorMessage; 35 import com.uber.nullaway.Nullness; 36 import com.uber.nullaway.fixserialization.location.SymbolLocation; 37 import com.uber.nullaway.fixserialization.out.ErrorInfo; 38 import com.uber.nullaway.fixserialization.out.SuggestedNullableFixInfo; 39 import java.util.Map; 40 import java.util.regex.Matcher; 41 import java.util.regex.Pattern; 42 import javax.annotation.Nullable; 43 44 /** A facade class to interact with fix serialization package. */ 45 public class SerializationService { 46 47 /** Special characters that need to be escaped in TSV files. */ 48 private static final ImmutableMap<Character, Character> escapes = 49 ImmutableMap.of( 50 '\n', 'n', 51 '\t', 't', 52 '\f', 'f', 53 '\b', 'b', 54 '\r', 'r'); 55 56 /** 57 * Escapes special characters in string to conform with TSV file formats. The most common 58 * convention for lossless conversion is to escape special characters with a backslash according 59 * to <a 60 * href="https://en.wikipedia.org/wiki/Tab-separated_values#Conventions_for_lossless_conversion_to_TSV"> 61 * Conventions for lossless conversion to TSV</a> 62 * 63 * @param str String to process. 64 * @return returns modified str which its special characters are escaped. 65 */ escapeSpecialCharacters(String str)66 public static String escapeSpecialCharacters(String str) { 67 // regex needs "\\" to match character '\', each must also be escaped in string to create "\\", 68 // therefore we need four "\". 69 // escape existing backslashes 70 str = str.replaceAll(Pattern.quote("\\"), Matcher.quoteReplacement("\\\\")); 71 // escape special characters 72 for (Map.Entry<Character, Character> entry : escapes.entrySet()) { 73 str = 74 str.replaceAll( 75 String.valueOf(entry.getKey()), Matcher.quoteReplacement("\\" + entry.getValue())); 76 } 77 return str; 78 } 79 80 /** 81 * Serializes the suggested type change of an element in the source code that can resolve the 82 * error. We do not want suggested fix changes to override explicit annotations in the code, 83 * therefore, if the target element has an explicit {@code @Nonnull} annotation, no type change is 84 * suggested. 85 * 86 * @param config NullAway config. 87 * @param state Visitor state. 88 * @param target Target element to alternate it's type. 89 * @param errorMessage Error caused by the target. 90 */ serializeFixSuggestion( Config config, VisitorState state, Symbol target, ErrorMessage errorMessage)91 public static void serializeFixSuggestion( 92 Config config, VisitorState state, Symbol target, ErrorMessage errorMessage) { 93 FixSerializationConfig serializationConfig = config.getSerializationConfig(); 94 if (!serializationConfig.suggestEnabled) { 95 return; 96 } 97 // Skip if the element has an explicit @Nonnull annotation. 98 if (Nullness.hasNonNullAnnotation(target, config)) { 99 return; 100 } 101 Trees trees = Trees.instance(JavacProcessingEnvironment.instance(state.context)); 102 // Skip if the element is received as bytecode. 103 if (trees.getPath(target) == null) { 104 return; 105 } 106 SymbolLocation location = SymbolLocation.createLocationFromSymbol(target); 107 SuggestedNullableFixInfo suggestedNullableFixInfo = 108 buildFixMetadata(state.getPath(), errorMessage, location); 109 Serializer serializer = serializationConfig.getSerializer(); 110 Preconditions.checkNotNull( 111 serializer, "Serializer shouldn't be null at this point, error in configuration setting!"); 112 serializer.serializeSuggestedFixInfo( 113 suggestedNullableFixInfo, serializationConfig.suggestEnclosing); 114 } 115 116 /** 117 * Serializes the reporting error. 118 * 119 * @param config NullAway config. 120 * @param state Visitor state. 121 * @param errorTree Tree of the element involved in the reporting error. 122 * @param errorMessage Error caused by the target. 123 */ serializeReportingError( Config config, VisitorState state, Tree errorTree, @Nullable Symbol target, ErrorMessage errorMessage)124 public static void serializeReportingError( 125 Config config, 126 VisitorState state, 127 Tree errorTree, 128 @Nullable Symbol target, 129 ErrorMessage errorMessage) { 130 Serializer serializer = config.getSerializationConfig().getSerializer(); 131 Preconditions.checkNotNull( 132 serializer, "Serializer shouldn't be null at this point, error in configuration setting!"); 133 serializer.serializeErrorInfo(new ErrorInfo(state.getPath(), errorTree, errorMessage, target)); 134 } 135 136 /** 137 * Builds the {@link SuggestedNullableFixInfo} instance based on the {@link ErrorMessage} type. 138 */ buildFixMetadata( TreePath path, ErrorMessage errorMessage, SymbolLocation location)139 private static SuggestedNullableFixInfo buildFixMetadata( 140 TreePath path, ErrorMessage errorMessage, SymbolLocation location) { 141 SuggestedNullableFixInfo suggestedNullableFixInfo; 142 switch (errorMessage.getMessageType()) { 143 case RETURN_NULLABLE: 144 case WRONG_OVERRIDE_RETURN: 145 case WRONG_OVERRIDE_PARAM: 146 case PASS_NULLABLE: 147 case FIELD_NO_INIT: 148 case ASSIGN_FIELD_NULLABLE: 149 case METHOD_NO_INIT: 150 suggestedNullableFixInfo = new SuggestedNullableFixInfo(path, location, errorMessage); 151 break; 152 default: 153 throw new IllegalStateException( 154 "Cannot suggest a type to resolve error of type: " + errorMessage.getMessageType()); 155 } 156 return suggestedNullableFixInfo; 157 } 158 } 159