blob: 49ba811ee3a6c5f3a26f5b3d2b20402f8df6a90a [file] [log] [blame]
/*
* Copyright (c) 2017 Uber Technologies, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.uber.nullaway;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.ErrorProneFlags;
import com.uber.nullaway.fixserialization.FixSerializationConfig;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
/**
* provides nullability configuration based on additional flags passed to ErrorProne via
* "-XepOpt:[Namespace:]FlagName[=Value]". See. http://errorprone.info/docs/flags
*/
final class ErrorProneCLIFlagsConfig extends AbstractConfig {
private static final String BASENAME_REGEX = ".*/([^/]+)\\.[ja]ar$";
static final String EP_FL_NAMESPACE = "NullAway";
static final String FL_ANNOTATED_PACKAGES = EP_FL_NAMESPACE + ":AnnotatedPackages";
static final String FL_ASSERTS_ENABLED = EP_FL_NAMESPACE + ":AssertsEnabled";
static final String FL_UNANNOTATED_SUBPACKAGES = EP_FL_NAMESPACE + ":UnannotatedSubPackages";
static final String FL_CLASSES_TO_EXCLUDE = EP_FL_NAMESPACE + ":ExcludedClasses";
static final String FL_EXHAUSTIVE_OVERRIDE = EP_FL_NAMESPACE + ":ExhaustiveOverride";
static final String FL_KNOWN_INITIALIZERS = EP_FL_NAMESPACE + ":KnownInitializers";
static final String FL_CLASS_ANNOTATIONS_TO_EXCLUDE =
EP_FL_NAMESPACE + ":ExcludedClassAnnotations";
static final String FL_SUGGEST_SUPPRESSIONS = EP_FL_NAMESPACE + ":SuggestSuppressions";
static final String FL_CLASS_ANNOTATIONS_GENERATED =
EP_FL_NAMESPACE + ":CustomGeneratedCodeAnnotations";
static final String FL_GENERATED_UNANNOTATED = EP_FL_NAMESPACE + ":TreatGeneratedAsUnannotated";
static final String FL_ACKNOWLEDGE_ANDROID_RECENT = EP_FL_NAMESPACE + ":AcknowledgeAndroidRecent";
static final String FL_EXCLUDED_FIELD_ANNOT = EP_FL_NAMESPACE + ":ExcludedFieldAnnotations";
static final String FL_INITIALIZER_ANNOT = EP_FL_NAMESPACE + ":CustomInitializerAnnotations";
static final String FL_NULLABLE_ANNOT = EP_FL_NAMESPACE + ":CustomNullableAnnotations";
static final String FL_NONNULL_ANNOT = EP_FL_NAMESPACE + ":CustomNonnullAnnotations";
static final String FL_CTNN_METHOD = EP_FL_NAMESPACE + ":CastToNonNullMethod";
static final String FL_EXTERNAL_INIT_ANNOT = EP_FL_NAMESPACE + ":ExternalInitAnnotations";
static final String FL_CONTRACT_ANNOT = EP_FL_NAMESPACE + ":CustomContractAnnotations";
static final String FL_UNANNOTATED_CLASSES = EP_FL_NAMESPACE + ":UnannotatedClasses";
static final String FL_ACKNOWLEDGE_RESTRICTIVE =
EP_FL_NAMESPACE + ":AcknowledgeRestrictiveAnnotations";
static final String FL_CHECK_OPTIONAL_EMPTINESS = EP_FL_NAMESPACE + ":CheckOptionalEmptiness";
static final String FL_CHECK_CONTRACTS = EP_FL_NAMESPACE + ":CheckContracts";
static final String FL_HANDLE_TEST_ASSERTION_LIBRARIES =
EP_FL_NAMESPACE + ":HandleTestAssertionLibraries";
static final String FL_OPTIONAL_CLASS_PATHS =
EP_FL_NAMESPACE + ":CheckOptionalEmptinessCustomClasses";
static final String FL_SUPPRESS_COMMENT = EP_FL_NAMESPACE + ":AutoFixSuppressionComment";
/** --- JarInfer configs --- */
static final String FL_JI_ENABLED = EP_FL_NAMESPACE + ":JarInferEnabled";
static final String FL_JI_USE_RETURN = EP_FL_NAMESPACE + ":JarInferUseReturnAnnotations";
static final String FL_JI_REGEX_MODEL_PATH = EP_FL_NAMESPACE + ":JarInferRegexStripModelJar";
static final String FL_JI_REGEX_CODE_PATH = EP_FL_NAMESPACE + ":JarInferRegexStripCodeJar";
static final String FL_ERROR_URL = EP_FL_NAMESPACE + ":ErrorURL";
/** --- Serialization configs --- */
static final String FL_FIX_SERIALIZATION = EP_FL_NAMESPACE + ":SerializeFixMetadata";
static final String FL_FIX_SERIALIZATION_CONFIG_PATH =
EP_FL_NAMESPACE + ":FixSerializationConfigPath";
static final String FL_ACKNOWLEDGE_LIBRARY_MODELS_OF_ANNOTATED_CODE =
EP_FL_NAMESPACE + ":AcknowledgeLibraryModelsOfAnnotatedCode";
private static final String DELIMITER = ",";
static final ImmutableSet<String> DEFAULT_CLASS_ANNOTATIONS_TO_EXCLUDE =
ImmutableSet.of("lombok.Generated");
// Annotations with simple name ".Generated" need not be manually listed, and are always matched
// by default
// TODO: org.apache.avro.specific.AvroGenerated should go here, but we are skipping it for the
// next release to better test the effect of this feature (users can always manually configure
// it).
static final ImmutableSet<String> DEFAULT_CLASS_ANNOTATIONS_GENERATED = ImmutableSet.of();
static final ImmutableSet<String> DEFAULT_KNOWN_INITIALIZERS =
ImmutableSet.of(
"android.view.View.onFinishInflate",
"android.app.Service.onCreate",
"android.app.Activity.onCreate",
"android.app.Fragment.onCreate",
"android.app.Fragment.onAttach",
"android.app.Fragment.onCreateView",
"android.app.Fragment.onViewCreated",
"android.app.Application.onCreate",
"javax.annotation.processing.Processor.init",
// Support Library v4 - can be removed once AndroidX becomes more popular
"android.support.v4.app.ActivityCompat.onCreate",
"android.support.v4.app.Fragment.onCreate",
"android.support.v4.app.Fragment.onAttach",
"android.support.v4.app.Fragment.onCreateView",
"android.support.v4.app.Fragment.onViewCreated",
// Support Library v4 - can be removed once AndroidX becomes more popular
"androidx.core.app.ActivityCompat.onCreate",
"androidx.fragment.app.Fragment.onCreate",
"androidx.fragment.app.Fragment.onAttach",
"androidx.fragment.app.Fragment.onCreateView",
"androidx.fragment.app.Fragment.onActivityCreated",
"androidx.fragment.app.Fragment.onViewCreated",
// Multidex app
"android.support.multidex.Application.onCreate");
static final ImmutableSet<String> DEFAULT_INITIALIZER_ANNOT =
ImmutableSet.of(
"org.junit.Before",
"org.junit.BeforeClass",
"org.junit.jupiter.api.BeforeAll",
"org.junit.jupiter.api.BeforeEach",
"org.springframework.beans.factory.annotation.Autowired");
// + Anything with @Initializer as its "simple name"
static final ImmutableSet<String> DEFAULT_EXTERNAL_INIT_ANNOT = ImmutableSet.of("lombok.Builder");
static final ImmutableSet<String> DEFAULT_CONTRACT_ANNOT =
ImmutableSet.of("org.jetbrains.annotations.Contract");
static final ImmutableSet<String> DEFAULT_EXCLUDED_FIELD_ANNOT =
ImmutableSet.of(
"jakarta.inject.Inject", // no explicit initialization when there is dependency injection
"javax.inject.Inject", // no explicit initialization when there is dependency injection
"com.google.errorprone.annotations.concurrent.LazyInit",
"org.checkerframework.checker.nullness.qual.MonotonicNonNull",
"org.springframework.beans.factory.annotation.Autowired");
private static final String DEFAULT_URL = "http://t.uber.com/nullaway";
ErrorProneCLIFlagsConfig(ErrorProneFlags flags) {
if (!flags.get(FL_ANNOTATED_PACKAGES).isPresent()) {
throw new IllegalStateException(
"DO NOT report an issue to Error Prone for this crash! NullAway configuration is "
+ "incorrect. "
+ "Must specify annotated packages, using the "
+ "-XepOpt:"
+ FL_ANNOTATED_PACKAGES
+ "=[...] flag. If you feel you have gotten this message in error report an issue"
+ " at https://github.com/uber/NullAway/issues.");
}
annotatedPackages = getPackagePattern(getFlagStringSet(flags, FL_ANNOTATED_PACKAGES));
unannotatedSubPackages = getPackagePattern(getFlagStringSet(flags, FL_UNANNOTATED_SUBPACKAGES));
sourceClassesToExclude = getFlagStringSet(flags, FL_CLASSES_TO_EXCLUDE);
unannotatedClasses = getFlagStringSet(flags, FL_UNANNOTATED_CLASSES);
knownInitializers =
getKnownInitializers(
getFlagStringSet(flags, FL_KNOWN_INITIALIZERS, DEFAULT_KNOWN_INITIALIZERS));
excludedClassAnnotations =
getFlagStringSet(
flags, FL_CLASS_ANNOTATIONS_TO_EXCLUDE, DEFAULT_CLASS_ANNOTATIONS_TO_EXCLUDE);
generatedCodeAnnotations =
getFlagStringSet(
flags, FL_CLASS_ANNOTATIONS_GENERATED, DEFAULT_CLASS_ANNOTATIONS_GENERATED);
initializerAnnotations =
getFlagStringSet(flags, FL_INITIALIZER_ANNOT, DEFAULT_INITIALIZER_ANNOT);
customNullableAnnotations = getFlagStringSet(flags, FL_NULLABLE_ANNOT, ImmutableSet.of());
customNonnullAnnotations = getFlagStringSet(flags, FL_NONNULL_ANNOT, ImmutableSet.of());
externalInitAnnotations =
getFlagStringSet(flags, FL_EXTERNAL_INIT_ANNOT, DEFAULT_EXTERNAL_INIT_ANNOT);
contractAnnotations = getFlagStringSet(flags, FL_CONTRACT_ANNOT, DEFAULT_CONTRACT_ANNOT);
isExhaustiveOverride = flags.getBoolean(FL_EXHAUSTIVE_OVERRIDE).orElse(false);
isSuggestSuppressions = flags.getBoolean(FL_SUGGEST_SUPPRESSIONS).orElse(false);
isAcknowledgeRestrictive = flags.getBoolean(FL_ACKNOWLEDGE_RESTRICTIVE).orElse(false);
checkOptionalEmptiness = flags.getBoolean(FL_CHECK_OPTIONAL_EMPTINESS).orElse(false);
checkContracts = flags.getBoolean(FL_CHECK_CONTRACTS).orElse(false);
handleTestAssertionLibraries =
flags.getBoolean(FL_HANDLE_TEST_ASSERTION_LIBRARIES).orElse(false);
treatGeneratedAsUnannotated = flags.getBoolean(FL_GENERATED_UNANNOTATED).orElse(false);
acknowledgeAndroidRecent = flags.getBoolean(FL_ACKNOWLEDGE_ANDROID_RECENT).orElse(false);
assertsEnabled = flags.getBoolean(FL_ASSERTS_ENABLED).orElse(false);
fieldAnnotPattern =
getPackagePattern(
getFlagStringSet(flags, FL_EXCLUDED_FIELD_ANNOT, DEFAULT_EXCLUDED_FIELD_ANNOT));
castToNonNullMethod = flags.get(FL_CTNN_METHOD).orElse(null);
autofixSuppressionComment = flags.get(FL_SUPPRESS_COMMENT).orElse("");
optionalClassPaths =
new ImmutableSet.Builder<String>()
.addAll(getFlagStringSet(flags, FL_OPTIONAL_CLASS_PATHS))
.add("java.util.Optional")
.build();
if (autofixSuppressionComment.contains("\n")) {
throw new IllegalStateException(
"Invalid -XepOpt:" + FL_SUPPRESS_COMMENT + " value. Comment must be single line.");
}
/** --- JarInfer configs --- */
jarInferEnabled = flags.getBoolean(FL_JI_ENABLED).orElse(false);
jarInferUseReturnAnnotations = flags.getBoolean(FL_JI_USE_RETURN).orElse(false);
// The defaults of these two options translate to: remove .aar/.jar from the file name, and also
// implicitly mean that NullAway will search for jarinfer models in the same jar which contains
// the analyzed classes.
jarInferRegexStripModelJarName = flags.get(FL_JI_REGEX_MODEL_PATH).orElse(BASENAME_REGEX);
jarInferRegexStripCodeJarName = flags.get(FL_JI_REGEX_CODE_PATH).orElse(BASENAME_REGEX);
errorURL = flags.get(FL_ERROR_URL).orElse(DEFAULT_URL);
if (acknowledgeAndroidRecent && !isAcknowledgeRestrictive) {
throw new IllegalStateException(
"-XepOpt:"
+ FL_ACKNOWLEDGE_ANDROID_RECENT
+ " should only be set when -XepOpt:"
+ FL_ACKNOWLEDGE_RESTRICTIVE
+ " is also set");
}
serializationActivationFlag = flags.getBoolean(FL_FIX_SERIALIZATION).orElse(false);
Optional<String> fixSerializationConfigPath = flags.get(FL_FIX_SERIALIZATION_CONFIG_PATH);
if (serializationActivationFlag && !fixSerializationConfigPath.isPresent()) {
throw new IllegalStateException(
"DO NOT report an issue to Error Prone for this crash! NullAway Fix Serialization configuration is "
+ "incorrect. "
+ "Must specify AutoFixer Output Directory, using the "
+ "-XepOpt:"
+ FL_FIX_SERIALIZATION_CONFIG_PATH
+ " flag. If you feel you have gotten this message in error report an issue"
+ " at https://github.com/uber/NullAway/issues.");
}
/*
* if fixSerializationActivationFlag is false, the default constructor is invoked for
* creating FixSerializationConfig which all features are deactivated. This lets the
* field be @Nonnull, allowing us to avoid null checks in various places.
*/
fixSerializationConfig =
serializationActivationFlag && fixSerializationConfigPath.isPresent()
? new FixSerializationConfig(fixSerializationConfigPath.get())
: new FixSerializationConfig();
if (serializationActivationFlag && isSuggestSuppressions) {
throw new IllegalStateException(
"In order to activate Fix Serialization mode ("
+ FL_FIX_SERIALIZATION
+ "), Suggest Suppressions mode must be deactivated ("
+ FL_SUGGEST_SUPPRESSIONS
+ ")");
}
acknowledgeLibraryModelsOfAnnotatedCode =
flags.getBoolean(FL_ACKNOWLEDGE_LIBRARY_MODELS_OF_ANNOTATED_CODE).orElse(false);
}
private static ImmutableSet<String> getFlagStringSet(ErrorProneFlags flags, String flagName) {
Optional<String> flagValue = flags.get(flagName);
if (flagValue.isPresent()) {
return ImmutableSet.copyOf(flagValue.get().split(DELIMITER));
}
return ImmutableSet.of();
}
private static ImmutableSet<String> getFlagStringSet(
ErrorProneFlags flags, String flagName, ImmutableSet<String> defaults) {
Set<String> combined = new LinkedHashSet<>(defaults);
Optional<String> flagValue = flags.get(flagName);
if (flagValue.isPresent()) {
Collections.addAll(combined, flagValue.get().split(DELIMITER));
}
return ImmutableSet.copyOf(combined);
}
}