diff --git a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StaticImportTest.java b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StaticImportTest.java index 78f8bad..a814439 100644 --- a/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StaticImportTest.java +++ b/org.eclipse.jdt.core.tests.compiler/src/org/eclipse/jdt/core/tests/compiler/regression/StaticImportTest.java @@ -2889,6 +2889,193 @@ "p1.Bar.B\n" + "p1.Bar.B"); } + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=361327 + // To verify that all static members are imported with a single static import statement + public void test085() { + this.runNegativeTest( + new String[] { + "Test.java", + "import static p1.Bar.B;\n" + + "import static p3.Foo.B;\n" + + "public class Test {\n" + + " public static void main(String [] args){\n" + + " new Test().test2();" + + " }\n" + + " public void test2(){\n" + + " System.out.println(B.class.getCanonicalName().toString());\n" + + " System.out.println(p1.Bar.B.class.getCanonicalName().toString());" + + " }\n" + + "}\n", + "p1/Bar.java", + "package p1;\n" + + "public class Bar{\n" + + " public static class B{}\n" + + " public static String B = new String(\"random\");\n" + + "}\n", + "p3/Foo.java", + "package p3;\n" + + "public class Foo {\n" + + " public static class B{\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. ERROR in Test.java (at line 2)\n" + + " import static p3.Foo.B;\n" + + " ^^^^^^^^\n" + + "The import p3.Foo.B collides with another import statement\n" + + "----------\n"); + } + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=361327 + // To verify that all static members are imported with a single static import statement, + // even from a supertype + public void test085a() { + this.runNegativeTest( + new String[] { + "Test.java", + "import static p1.Bar.B;\n" + + "import static p3.Foo.B;\n" + + "public class Test {\n" + + " public static void main(String [] args){\n" + + " new Test().test2();" + + " }\n" + + " public void test2(){\n" + + " System.out.println(B.class.getCanonicalName().toString());\n" + + " System.out.println(p1.Bar.B.class.getCanonicalName().toString());" + + " }\n" + + "}\n", + "p1/Bar.java", + "package p1;\n" + + "public class Bar extends SuperBar{\n" + + " public static void B(){}\n" + + "}\n", + "p1/SuperBar.java", + "package p1;\n" + + "public class SuperBar {\n" + + " public static class B{}\n" + + " final public static String B = new String(\"random\");\n" + + "}\n", + "p3/Foo.java", + "package p3;\n" + + "public class Foo {\n" + + " public static class B{\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. ERROR in Test.java (at line 2)\n" + + " import static p3.Foo.B;\n" + + " ^^^^^^^^\n" + + "The import p3.Foo.B collides with another import statement\n" + + "----------\n"); + } + + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=361327 + // To verify that all static members are imported with a single static import statement + // this tests checks collision with single type import + public void test085b() { + this.runNegativeTest( + new String[] { + "Test.java", + "import static p1.Bar.B;\n" + + "import p3.Foo.B;\n" + + "public class Test {\n" + + " public static void main(String [] args){\n" + + " new Test().test2();" + + " }\n" + + " public void test2(){\n" + + " System.out.println(B.class.getCanonicalName().toString());\n" + + " System.out.println(p1.Bar.B.class.getCanonicalName().toString());" + + " }\n" + + "}\n", + "p1/Bar.java", + "package p1;\n" + + "public class Bar{\n" + + " public static class B{}\n" + + " public static String B = new String(\"random\");\n" + + "}\n", + "p3/Foo.java", + "package p3;\n" + + "public class Foo {\n" + + " public class B{\n" + + " }\n" + + "}\n" + }, + "----------\n" + + "1. ERROR in Test.java (at line 2)\n" + + " import p3.Foo.B;\n" + + " ^^^^^^^^\n" + + "The import p3.Foo.B collides with another import statement\n" + + "----------\n"); + } + + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=361327 + // To verify that all static members are imported with a single static import statement + // this tests checks collision with top level type + public void test085c() { + this.runNegativeTest( + new String[] { + "Test.java", + "import static p1.Bar.B;\n" + + "public class Test {\n" + + " public static void main(String [] args){\n" + + " new Test().test2();" + + " }\n" + + " public void test2(){\n" + + " System.out.println(B.class.getCanonicalName().toString());\n" + + " System.out.println(p1.Bar.B.class.getCanonicalName().toString());" + + " }\n" + + "}\n" + + "class B{\n" + + "}\n", + "p1/Bar.java", + "package p1;\n" + + "public class Bar{\n" + + " public static class B{}\n" + + " public static String B = new String(\"random\");\n" + + "}\n", + }, + "----------\n" + + "1. ERROR in Test.java (at line 1)\n" + + " import static p1.Bar.B;\n" + + " ^^^^^^^^\n" + + "The import p1.Bar.B conflicts with a type defined in the same file\n" + + "----------\n"); + } + + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=361327 + // Test obscuring rules defined in JLS 7.5.3 + public void test086() { + this.runConformTest( + new String[] { + "Test.java", + "import static p1.Bar.B;\n" + + "import static p3.Foo.*;\n" + + "public class Test {\n" + + " public static void main(String [] args){\n" + + " new Test().test2();" + + " }\n" + + " public void test2(){\n" + + " B();\n" + // should be p1.Bar.B() and not p3.Foo.B() + " System.out.println(B.toString());\n" + // should be p1.Bar.B + " }\n" + + "}\n", + "p1/Bar.java", + "package p1;\n" + + "public class Bar{\n" + + " public static void B(){ System.out.println(\"Bar's method B\");}\n" + + " public static String B = new String(\"Bar's field B\");\n" + + "}\n", + "p3/Foo.java", + "package p3;\n" + + "public class Foo {\n" + + " public static void B(){ System.out.println(\"Foo's method B\");}\n" + + " public static String B = new String(\"Foo's field B\");\n" + + "}\n" + }, + "Bar\'s method B\n" + + "Bar\'s field B"); + } } diff --git a/org.eclipse.jdt.core/buildnotes_jdt-core.html b/org.eclipse.jdt.core/buildnotes_jdt-core.html index 901e598..e99f269 100644 --- a/org.eclipse.jdt.core/buildnotes_jdt-core.html +++ b/org.eclipse.jdt.core/buildnotes_jdt-core.html @@ -52,7 +52,9 @@

What's new in this drop

Problem Reports Fixed

-346042 +361327 +Static import resolution does not record all static elements being imported +
346042 [1.5][compiler] ecj compiles code rejected by javac for varargs parameters of inaccessible type
361938 Formerly working JLS3 parser not working -- Scanner reports com.sun.jdi.InvocationException occurred invoking method. diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java index 32ccfd8..567c934 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/CompilationUnitScope.java @@ -25,6 +25,7 @@ public char[][] currentPackageName; public PackageBinding fPackage; public ImportBinding[] imports; + public int importPtr; public HashtableOfObject typeOrPackageCache; // used in Scope.getTypeOrPackage() public SourceTypeBinding[] topLevelTypes; @@ -37,6 +38,8 @@ HashtableOfType constantPoolNameUsage; private int captureID = 1; + + private ImportBinding[] tempImports; // to keep a record of resolved imports while traversing all in faultInImports() public CompilationUnitScope(CompilationUnitDeclaration unit, LookupEnvironment environment) { super(COMPILATION_UNIT_SCOPE, null); @@ -328,10 +331,10 @@ break; } } - ImportBinding[] resolvedImports = new ImportBinding[numberOfImports]; - resolvedImports[0] = getDefaultImports()[0]; - int index = 1; - + this.tempImports = new ImportBinding[numberOfImports]; + this.tempImports[0] = getDefaultImports()[0]; + this.importPtr = 1; + // keep static imports with normal imports until there is a reason to split them up // on demand imports continue to be packages & types. need to check on demand type imports for fields/methods // single imports change from being just types to types or fields @@ -340,8 +343,8 @@ char[][] compoundName = importReference.tokens; // skip duplicates or imports of the current package - for (int j = 0; j < index; j++) { - ImportBinding resolved = resolvedImports[j]; + for (int j = 0; j < this.importPtr; j++) { + ImportBinding resolved = this.tempImports[j]; if (resolved.onDemand == ((importReference.bits & ASTNode.OnDemand) != 0) && resolved.isStatic() == importReference.isStatic()) { if (CharOperation.equals(compoundName, resolved.compoundName)) { problemReporter().unusedImport(importReference); // since skipped, must be reported now @@ -364,7 +367,7 @@ problemReporter().cannotImportPackage(importReference); continue nextImport; } - resolvedImports[index++] = new ImportBinding(compoundName, true, importBinding, importReference); + recordImportBinding(new ImportBinding(compoundName, true, importBinding, importReference)); } else { Binding importBinding = findSingleImport(compoundName, Binding.TYPE | Binding.FIELD | Binding.METHOD, importReference.isStatic()); if (!importBinding.isValidBinding()) { @@ -379,81 +382,28 @@ problemReporter().cannotImportPackage(importReference); continue nextImport; } - ReferenceBinding conflictingType = null; - if (importBinding instanceof MethodBinding) { - conflictingType = (ReferenceBinding) getType(compoundName, compoundName.length); - if (!conflictingType.isValidBinding() || (importReference.isStatic() && !conflictingType.isStatic())) - conflictingType = null; - } - // collisions between an imported static field & a type should be checked according to spec... but currently not by javac - if (importBinding instanceof ReferenceBinding || conflictingType != null) { - ReferenceBinding referenceBinding = conflictingType == null ? (ReferenceBinding) importBinding : conflictingType; - ReferenceBinding typeToCheck = referenceBinding.problemId() == ProblemReasons.Ambiguous - ? ((ProblemReferenceBinding) referenceBinding).closestMatch - : referenceBinding; - if (importReference.isTypeUseDeprecated(typeToCheck, this)) - problemReporter().deprecatedType(typeToCheck, importReference); - - ReferenceBinding existingType = typesBySimpleNames.get(compoundName[compoundName.length - 1]); - if (existingType != null) { - // duplicate test above should have caught this case, but make sure - if (existingType == referenceBinding) { - // https://bugs.eclipse.org/bugs/show_bug.cgi?id=302865 - // Check all resolved imports to see if this import qualifies as a duplicate - for (int j = 0; j < index; j++) { - ImportBinding resolved = resolvedImports[j]; - if (resolved instanceof ImportConflictBinding) { - ImportConflictBinding importConflictBinding = (ImportConflictBinding) resolved; - if (importConflictBinding.conflictingTypeBinding == referenceBinding) { - if (!importReference.isStatic()) { - // resolved is implicitly static - problemReporter().duplicateImport(importReference); - resolvedImports[index++] = new ImportBinding(compoundName, false, importBinding, importReference); - } - } - } else if (resolved.resolvedImport == referenceBinding) { - if (importReference.isStatic() != resolved.isStatic()) { - problemReporter().duplicateImport(importReference); - resolvedImports[index++] = new ImportBinding(compoundName, false, importBinding, importReference); - } - } - } - continue nextImport; - } - // either the type collides with a top level type or another imported type - for (int j = 0, length = this.topLevelTypes.length; j < length; j++) { - if (CharOperation.equals(this.topLevelTypes[j].sourceName, existingType.sourceName)) { - problemReporter().conflictingImport(importReference); - continue nextImport; - } - } - problemReporter().duplicateImport(importReference); - continue nextImport; - } - typesBySimpleNames.put(compoundName[compoundName.length - 1], referenceBinding); - } else if (importBinding instanceof FieldBinding) { - for (int j = 0; j < index; j++) { - ImportBinding resolved = resolvedImports[j]; - // find other static fields with the same name - if (resolved.isStatic() && resolved.resolvedImport instanceof FieldBinding && importBinding != resolved.resolvedImport) { - if (CharOperation.equals(compoundName[compoundName.length - 1], resolved.compoundName[resolved.compoundName.length - 1])) { - problemReporter().duplicateImport(importReference); - continue nextImport; - } - } + // all the code here which checks for valid bindings have been moved to the method + // checkAndRecordImportBinding() since bug 361327 + if(checkAndRecordImportBinding(importBinding, typesBySimpleNames, importReference, compoundName) == -1) + continue nextImport; + if (importReference.isStatic()) { + // look for more static bindings being imported by single static import(bug 361327). + // findSingleImport() finds fields first, followed by method and then type + // So if a type is found, no fields and methods are available anyway + // similarly when method is found, type may be available but no field available for sure + if (importBinding.kind() == Binding.FIELD) { + checkMoreStaticBindings(compoundName, typesBySimpleNames, Binding.TYPE | Binding.METHOD, importReference); + } else if (importBinding.kind() == Binding.METHOD) { + checkMoreStaticBindings(compoundName, typesBySimpleNames, Binding.TYPE, importReference); } } - resolvedImports[index++] = conflictingType == null - ? new ImportBinding(compoundName, false, importBinding, importReference) - : new ImportConflictBinding(compoundName, importBinding, conflictingType, importReference); } } // shrink resolvedImports... only happens if an error was reported - if (resolvedImports.length > index) - System.arraycopy(resolvedImports, 0, resolvedImports = new ImportBinding[index], 0, index); - this.imports = resolvedImports; - + if (this.tempImports.length > this.importPtr) + System.arraycopy(this.tempImports, 0, this.tempImports = new ImportBinding[this.importPtr], 0, this.importPtr); + this.imports = this.tempImports; int length = this.imports.length; this.typeOrPackageCache = new HashtableOfObject(length); for (int i = 0; i < length; i++) { @@ -840,4 +790,129 @@ for (int i = 0, length = this.topLevelTypes.length; i < length; i++) this.topLevelTypes[i].verifyMethods(verifier); } +private void recordImportBinding(ImportBinding bindingToAdd) { + if (this.tempImports.length == this.importPtr) { + System.arraycopy(this.tempImports, 0, (this.tempImports = new ImportBinding[this.importPtr + 1]), 0, this.importPtr); + } + this.tempImports[this.importPtr++] = bindingToAdd; +} +/** + * Checks additional bindings (methods or types) imported from a single static import. + * Method is tried first, followed by type. If found, records them. + * If in the process, import is flagged as duplicate, -1 is returned. + * @param compoundName + * @param typesBySimpleNames + * @param mask + * @param importReference + */ +private void checkMoreStaticBindings( + char[][] compoundName, + HashtableOfType typesBySimpleNames, + int mask, + ImportReference importReference) { + Binding importBinding = findSingleStaticImport(compoundName, mask); + if (!importBinding.isValidBinding()) { + // only continue if the same kind's ambiguous binding is returned + // may have found an ambiguous type when looking for field or method. Don't continue in that case + if (importBinding.problemId() == ProblemReasons.Ambiguous) { + // keep it unless a duplicate can be found below + checkAndRecordImportBinding(importBinding, typesBySimpleNames, importReference, compoundName); + } + } else { + checkAndRecordImportBinding(importBinding, typesBySimpleNames, importReference, compoundName); + } + if (((mask & Binding.METHOD) != 0) && (importBinding.kind() == Binding.METHOD)) { + // found method + // type is left to be looked for + // reset METHOD bit to enable lookup for only type + mask &= ~Binding.METHOD; + // now search for a type binding + checkMoreStaticBindings(compoundName, typesBySimpleNames, mask, importReference); + } +} +/** + * Checks for duplicates. If all ok, records the importBinding + * returns -1 when this import is flagged as duplicate. + * @param importBinding + * @param typesBySimpleNames + * @param importReference + * @param compoundName + * @return -1 when this import is flagged as duplicate, importPtr otherwise. + */ +private int checkAndRecordImportBinding( + Binding importBinding, + HashtableOfType typesBySimpleNames, + ImportReference importReference, + char[][] compoundName) { + ReferenceBinding conflictingType = null; + if (importBinding instanceof MethodBinding) { + conflictingType = (ReferenceBinding) getType(compoundName, compoundName.length); + if (!conflictingType.isValidBinding() || (importReference.isStatic() && !conflictingType.isStatic())) + conflictingType = null; + } + // collisions between an imported static field & a type should be checked according to spec... but currently not by javac + if (importBinding instanceof ReferenceBinding || conflictingType != null) { + ReferenceBinding referenceBinding = conflictingType == null ? (ReferenceBinding) importBinding : conflictingType; + ReferenceBinding typeToCheck = referenceBinding.problemId() == ProblemReasons.Ambiguous + ? ((ProblemReferenceBinding) referenceBinding).closestMatch + : referenceBinding; + if (importReference.isTypeUseDeprecated(typeToCheck, this)) + problemReporter().deprecatedType(typeToCheck, importReference); + + ReferenceBinding existingType = typesBySimpleNames.get(compoundName[compoundName.length - 1]); + if (existingType != null) { + // duplicate test above should have caught this case, but make sure + if (existingType == referenceBinding) { + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=302865 + // Check all resolved imports to see if this import qualifies as a duplicate + for (int j = 0; j < this.importPtr; j++) { + ImportBinding resolved = this.tempImports[j]; + if (resolved instanceof ImportConflictBinding) { + ImportConflictBinding importConflictBinding = (ImportConflictBinding) resolved; + if (importConflictBinding.conflictingTypeBinding == referenceBinding) { + if (!importReference.isStatic()) { + // resolved is implicitly static + problemReporter().duplicateImport(importReference); + recordImportBinding(new ImportBinding(compoundName, false, importBinding, importReference)); + } + } + } else if (resolved.resolvedImport == referenceBinding) { + if (importReference.isStatic() != resolved.isStatic()) { + problemReporter().duplicateImport(importReference); + recordImportBinding(new ImportBinding(compoundName, false, importBinding, importReference)); + } + } + } + return -1; + } + // either the type collides with a top level type or another imported type + for (int j = 0, length = this.topLevelTypes.length; j < length; j++) { + if (CharOperation.equals(this.topLevelTypes[j].sourceName, existingType.sourceName)) { + problemReporter().conflictingImport(importReference); + return -1; + } + } + problemReporter().duplicateImport(importReference); + return -1; + } + typesBySimpleNames.put(compoundName[compoundName.length - 1], referenceBinding); + } else if (importBinding instanceof FieldBinding) { + for (int j = 0; j < this.importPtr; j++) { + ImportBinding resolved = this.tempImports[j]; + // find other static fields with the same name + if (resolved.isStatic() && resolved.resolvedImport instanceof FieldBinding && importBinding != resolved.resolvedImport) { + if (CharOperation.equals(compoundName[compoundName.length - 1], resolved.compoundName[resolved.compoundName.length - 1])) { + problemReporter().duplicateImport(importReference); + return -1; + } + } + } + } + if (conflictingType == null) { + recordImportBinding(new ImportBinding(compoundName, false, importBinding, importReference)); + } else { + recordImportBinding(new ImportConflictBinding(compoundName, importBinding, conflictingType, importReference)); + } + return this.importPtr; +} }