| /******************************************************************************* |
| * Copyright (c) 2017, 2018 GK Software AG, and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Stephan Herrmann - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.tests.compiler.regression; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.core.tests.util.Util; |
| import org.eclipse.jdt.internal.compiler.CompilationResult; |
| import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; |
| import org.eclipse.jdt.internal.compiler.batch.BasicModule; |
| import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; |
| import org.eclipse.jdt.internal.compiler.batch.FileSystem; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; |
| import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; |
| import org.eclipse.jdt.internal.compiler.env.IModule; |
| import org.eclipse.jdt.internal.compiler.env.INameEnvironment; |
| import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; |
| import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; |
| import org.eclipse.jdt.internal.compiler.parser.Parser; |
| import org.eclipse.jdt.internal.compiler.problem.ProblemReporter; |
| |
| import junit.framework.Test; |
| |
| import static org.eclipse.jdt.core.tests.util.Util.createJar; |
| |
| public class NullAnnotationTests9 extends AbstractNullAnnotationTest { |
| |
| public NullAnnotationTests9(String name) { |
| super(name); |
| } |
| |
| static { |
| // TESTS_NAMES = new String[] { "testBug456497" }; |
| // TESTS_NUMBERS = new int[] { 001 }; |
| // TESTS_RANGE = new int[] { 1, 12 }; |
| } |
| |
| public static Test suite() { |
| return buildMinimalComplianceTestSuite(testClass(), F_9); |
| } |
| |
| public static Class<?> testClass() { |
| return NullAnnotationTests9.class; |
| } |
| |
| @Deprecated // super method is deprecated |
| @Override |
| protected void setUpAnnotationLib() throws IOException { |
| if (this.LIBS == null) { |
| String[] defaultLibs = getDefaultClassPaths(); |
| int len = defaultLibs.length; |
| this.LIBS = new String[len+1]; |
| System.arraycopy(defaultLibs, 0, this.LIBS, 0, len); |
| this.LIBS[len] = createAnnotation_2_2_jar(Util.getOutputDirectory() + File.separator, null); |
| } |
| } |
| |
| public static String createAnnotation_2_2_jar(String dirName, String jcl9Path) throws IOException { |
| // role our own annotation library as long as o.e.j.annotation is still at BREE 1.8: |
| String jarFileName = dirName + "org.eclipse.jdt.annotation_2.2.0.jar"; |
| createJar(new String[] { |
| "module-info.java", |
| "module org.eclipse.jdt.annotation {\n" + |
| " exports org.eclipse.jdt.annotation;\n" + |
| "}\n", |
| |
| "org/eclipse/jdt/annotation/DefaultLocation.java", |
| "package org.eclipse.jdt.annotation;\n" + |
| "\n" + |
| "public enum DefaultLocation {\n" + |
| " \n" + |
| " PARAMETER, RETURN_TYPE, FIELD, TYPE_PARAMETER, TYPE_BOUND, TYPE_ARGUMENT, ARRAY_CONTENTS\n" + |
| "}\n", |
| |
| "org/eclipse/jdt/annotation/NonNullByDefault.java", |
| "package org.eclipse.jdt.annotation;\n" + |
| "\n" + |
| "import java.lang.annotation.ElementType;\n" + |
| "import static org.eclipse.jdt.annotation.DefaultLocation.*;\n" + |
| "\n" + |
| "import java.lang.annotation.*;\n" + |
| " \n" + |
| "@Documented\n" + |
| "@Retention(RetentionPolicy.CLASS)\n" + |
| "@Target({ ElementType.MODULE, ElementType.PACKAGE, ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE })\n" + |
| "public @interface NonNullByDefault {\n" + |
| " DefaultLocation[] value() default { PARAMETER, RETURN_TYPE, FIELD, TYPE_BOUND, TYPE_ARGUMENT };\n" + |
| "}", |
| |
| "org/eclipse/jdt/annotation/NonNull.java", |
| "package org.eclipse.jdt.annotation;\n" + |
| "import static java.lang.annotation.ElementType.TYPE_USE;\n" + |
| "\n" + |
| "import java.lang.annotation.*;\n" + |
| " \n" + |
| "@Documented\n" + |
| "@Retention(RetentionPolicy.CLASS)\n" + |
| "@Target({ TYPE_USE })\n" + |
| "public @interface NonNull {\n" + |
| " // marker annotation with no members\n" + |
| "}\n", |
| |
| "org/eclipse/jdt/annotation/Nullable.java", |
| "package org.eclipse.jdt.annotation;\n" + |
| "\n" + |
| "import static java.lang.annotation.ElementType.TYPE_USE;\n" + |
| "\n" + |
| "import java.lang.annotation.*;\n" + |
| " \n" + |
| "@Documented\n" + |
| "@Retention(RetentionPolicy.CLASS)\n" + |
| "@Target({ TYPE_USE })\n" + |
| "public @interface Nullable {\n" + |
| " // marker annotation with no members\n" + |
| "}\n" |
| }, |
| null, |
| jarFileName, |
| jcl9Path != null ? new String[] { jcl9Path } : null, |
| "9"); |
| return jarFileName; |
| } |
| |
| // -------- internal infrastructure ------------ |
| |
| Map<String,IModule> moduleMap = new HashMap<>(); // by name |
| Map<String,String> file2module = new HashMap<>(); |
| |
| @Override |
| protected INameEnvironment getNameEnvironment(final String[] testFiles, String[] classPaths) { |
| this.classpaths = classPaths == null ? getDefaultClassPaths() : classPaths; |
| INameEnvironment[] classLibs = getClassLibs(classPaths == null); |
| for (INameEnvironment nameEnvironment : classLibs) { |
| ((FileSystem) nameEnvironment).scanForModules(createParser()); |
| } |
| return new InMemoryNameEnvironment9(testFiles, this.moduleMap, classLibs); |
| } |
| |
| // --- same as AbstractRegressionTest9, just in a different inheritance hierarchy: |
| |
| @Override |
| protected CompilationUnit[] getCompilationUnits(String[] testFiles) { |
| Map<String,char[]> moduleFiles= new HashMap<>(); // filename -> modulename |
| |
| // scan for all module-info.java: |
| for (int i = 0; i < testFiles.length; i+=2) { |
| IModule module = extractModuleDesc(testFiles[i], testFiles[i+1]); |
| if (module != null) { |
| this.moduleMap.put(String.valueOf(module.name()), module); |
| moduleFiles.put(testFiles[0], module.name()); |
| } |
| } |
| // record module information in CUs: |
| CompilationUnit[] compilationUnits = Util.compilationUnits(testFiles); |
| for (int i = 0; i < compilationUnits.length; i++) { |
| char[] fileName = compilationUnits[i].getFileName(); |
| String fileNameString = String.valueOf(compilationUnits[i].getFileName()); |
| if (CharOperation.endsWith(fileName, TypeConstants.MODULE_INFO_FILE_NAME)) { |
| compilationUnits[i].module = moduleFiles.get(fileNameString.replace(File.separator, "/")); |
| } else { |
| String modName = this.file2module.get(fileNameString.replace(File.separator, "/")); |
| if (modName != null) { |
| compilationUnits[i].module = modName.toCharArray(); |
| } |
| } |
| } |
| return compilationUnits; |
| } |
| |
| IModule extractModuleDesc(String fileName, String fileContent) { |
| if (fileName.toLowerCase().endsWith(IModule.MODULE_INFO_JAVA)) { |
| Parser parser = createParser(); |
| |
| ICompilationUnit cu = new CompilationUnit(fileContent.toCharArray(), fileName, null); |
| CompilationResult compilationResult = new CompilationResult(cu, 0, 1, 10); |
| CompilationUnitDeclaration unit = parser.parse(cu, compilationResult); |
| if (unit.isModuleInfo() && unit.moduleDeclaration != null) { |
| return new BasicModule(unit.moduleDeclaration, null); |
| } |
| } |
| return null; |
| } |
| |
| Parser createParser() { |
| Map<String,String> opts = new HashMap<String, String>(); |
| opts.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_9); |
| return new Parser( |
| new ProblemReporter(getErrorHandlingPolicy(), new CompilerOptions(opts), getProblemFactory()), |
| false); |
| } |
| |
| // ------------------------------------------------------ |
| |
| /** Use in tests to associate the CU in file 'fileName' to the module of the given name. */ |
| void associateToModule(String moduleName, String... fileNames) { |
| for (String fileName : fileNames) |
| this.file2module.put(fileName, moduleName); |
| } |
| |
| private Runner getDefaultRunner() { |
| Runner runner = new Runner(); |
| runner.classLibraries = this.LIBS; |
| runner.libsOnModulePath = true; |
| runner.javacTestOptions = |
| JavacTestOptions.Excuse.EclipseWarningConfiguredAsError; |
| return runner; |
| } |
| |
| public void test_nnbd_in_module_01() { |
| associateToModule("my.mod", "my.mod/p/X.java"); |
| Runner runner = getDefaultRunner(); |
| runner.testFiles = new String[] { |
| "my.mod/module-info.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "@NonNullByDefault\n" + |
| "module my.mod {\n" + |
| " requires static org.eclipse.jdt.annotation;\n" + |
| "}\n", |
| "my.mod/p/X.java", |
| "package p;\n" + |
| "public class X {\n" + |
| " String f; // missing nn init\n" + |
| " void foo(String s) {\n" + |
| " this.f = s; // OK\n" + |
| " }\n" + |
| "}\n" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. ERROR in my.mod\\p\\X.java (at line 3)\n" + |
| " String f; // missing nn init\n" + |
| " ^\n" + |
| "The @NonNull field f may not have been initialized\n" + |
| "----------\n"; |
| runner.runNegativeTest(); |
| } |
| |
| public void test_nnbd_in_module_02() throws IOException { |
| |
| String jarPath = OUTPUT_DIR+"/mod.one.jar"; |
| createJar( |
| new String[] { |
| "module-info.java", |
| "@org.eclipse.jdt.annotation.NonNullByDefault\n" + |
| "module mod.one {\n" + |
| " requires org.eclipse.jdt.annotation;\n" + |
| " exports p.q;\n" + |
| "}\n", |
| "p/q/API.java", |
| "package p.q;\n" + |
| "public class API {\n" + |
| " public String id(String in) { return in; }\n" + |
| "}\n" |
| }, |
| null, // extra path & content |
| jarPath, |
| this.LIBS, |
| "9"); |
| |
| associateToModule("my.mod", "my.mod/p/X.java"); |
| Runner runner = new Runner(); |
| runner.shouldFlushOutputDirectory = false; |
| runner.classLibraries = Arrays.copyOf(this.LIBS, this.LIBS.length+1); |
| runner.classLibraries[runner.classLibraries.length-1] = jarPath; |
| runner.libsOnModulePath = true; |
| runner.testFiles = new String[] { |
| "my.mod/module-info.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "@NonNullByDefault\n" + |
| "module my.mod {\n" + |
| " requires static org.eclipse.jdt.annotation;\n" + |
| " requires mod.one;\n" + |
| "}\n", |
| "my.mod/p/X.java", |
| "package p;\n" + |
| "import p.q.API;\n" + |
| "public class X {\n" + |
| " void foo(API api) {\n" + |
| " api.id(api.id(\"\")); // OK\n" + |
| " api.id(null); // NOK\n" + |
| " }\n" + |
| "}\n" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. ERROR in my.mod\\p\\X.java (at line 6)\n" + |
| " api.id(null); // NOK\n" + |
| " ^^^^\n" + |
| "Null type mismatch: required \'@NonNull String\' but the provided value is null\n" + |
| "----------\n"; |
| runner.javacTestOptions = |
| JavacTestOptions.Excuse.EclipseWarningConfiguredAsError; |
| runner.runNegativeTest(); |
| } |
| |
| public void test_redundant_nnbd_vs_module() { |
| associateToModule("my.mod", "my.mod/p/X.java", "my.mod/p2/package-info.java"); |
| Runner runner = getDefaultRunner(); |
| runner.testFiles = new String[] { |
| "my.mod/module-info.java", |
| "import org.eclipse.jdt.annotation.*;\n" + |
| "@NonNullByDefault\n" + |
| "module my.mod {\n" + |
| " requires static org.eclipse.jdt.annotation;\n" + |
| "}\n", |
| "my.mod/p/X.java", |
| "package p;\n" + |
| "@org.eclipse.jdt.annotation.NonNullByDefault\n" + |
| "public class X {\n" + |
| " String f; // missing nn init\n" + |
| " void foo(String s) {\n" + |
| " this.f = s; // OK\n" + |
| " }\n" + |
| "}\n", |
| "my.mod/p/Y.java", |
| "package p;\n" + |
| "import static org.eclipse.jdt.annotation.DefaultLocation.*;\n" + |
| "@org.eclipse.jdt.annotation.NonNullByDefault(PARAMETER)\n" + // not: FIELD, due to details not redundant |
| "public class Y {\n" + |
| " String f; // missing init is NOT a problem\n" + |
| " void foo(String s) {\n" + |
| " this.f = s; // OK\n" + |
| " }\n" + |
| "}\n", |
| "my.mod/p2/package-info.java", |
| "@org.eclipse.jdt.annotation.NonNullByDefault\n" + |
| "package p2;\n" |
| }; |
| runner.expectedCompilerLog = |
| "----------\n" + |
| "1. WARNING in my.mod\\p\\X.java (at line 2)\n" + |
| " @org.eclipse.jdt.annotation.NonNullByDefault\n" + |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + |
| "Nullness default is redundant with a default specified for the enclosing module my.mod\n" + |
| "----------\n" + |
| "2. ERROR in my.mod\\p\\X.java (at line 4)\n" + |
| " String f; // missing nn init\n" + |
| " ^\n" + |
| "The @NonNull field f may not have been initialized\n" + |
| "----------\n" + |
| "----------\n" + |
| "1. WARNING in my.mod\\p2\\package-info.java (at line 1)\n" + |
| " @org.eclipse.jdt.annotation.NonNullByDefault\n" + |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n" + |
| "Nullness default is redundant with a default specified for the enclosing module my.mod\n" + |
| "----------\n"; |
| runner.runNegativeTest(); |
| } |
| public void testBug536037a() { |
| if (this.complianceLevel < ClassFileConstants.JDK10) return; |
| runConformTestWithLibs( |
| new String[] { |
| "Bar.java", |
| "@org.eclipse.jdt.annotation.NonNullByDefault\n" + |
| "public class Bar {\n" + |
| " static void bar(Iterable<String> list) {\n" + |
| " for(var s : list);\n" + |
| " }\n" + |
| "}\n" |
| }, |
| null, |
| ""); |
| } |
| public void testBug536037b() { |
| // tests combination of declaration null-annotations & 'var': |
| if (this.complianceLevel < ClassFileConstants.JDK10) return; |
| Map<String, String> options = getCompilerOptions(); |
| options.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, "test.NonNull"); |
| options.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, "test.Nullable"); |
| runNegativeTestWithLibs( |
| new String[] { |
| "test/NonNull.java", |
| "package test;\n" + |
| "import java.lang.annotation.*;\n" + |
| "@Target({ElementType.LOCAL_VARIABLE,ElementType.PARAMETER}) public @interface NonNull {}\n", |
| "test/Nullable.java", |
| "package test;\n" + |
| "import java.lang.annotation.*;\n" + |
| "@Target({ElementType.LOCAL_VARIABLE,ElementType.PARAMETER}) public @interface Nullable {}\n", |
| "Bar.java", |
| "import test.*;\n" + |
| "public class Bar {\n" + |
| " static void bar1(@Nullable String s1, Iterable<String> list) {\n" + |
| " @NonNull var s2 = s1;\n" + |
| " for (@NonNull var s : list);\n" + |
| " }\n" + |
| " static void bar2(int[] array) {\n" + |
| " @NonNull var i1 = 3;\n" + |
| " for (@NonNull var s : array);\n" + |
| " }\n" + |
| "}\n" |
| }, |
| options, |
| "----------\n" + |
| "1. ERROR in Bar.java (at line 4)\n" + |
| " @NonNull var s2 = s1;\n" + |
| " ^^\n" + |
| "Null type mismatch: required \'@NonNull String\' but the provided value is specified as @Nullable\n" + |
| "----------\n" + |
| "2. WARNING in Bar.java (at line 5)\n" + |
| " for (@NonNull var s : list);\n" + |
| " ^^^^\n" + |
| "Null type safety: The expression of type \'String\' needs unchecked conversion to conform to \'@NonNull String\'\n" + |
| "----------\n" + |
| "3. ERROR in Bar.java (at line 8)\n" + |
| " @NonNull var i1 = 3;\n" + |
| " ^^^^^^^^\n" + |
| "The nullness annotation @NonNull is not applicable for the primitive type int\n" + |
| "----------\n" + |
| "4. ERROR in Bar.java (at line 9)\n" + |
| " for (@NonNull var s : array);\n" + |
| " ^^^^^^^^\n" + |
| "The nullness annotation @NonNull is not applicable for the primitive type int\n" + |
| "----------\n"); |
| } |
| } |