From 714070211387f0595cd8df499e20cf559116fcea Mon Sep 17 00:00:00 2001 From: Sebastian Zarnekow Date: Tue, 30 Jul 2019 18:26:44 +0200 Subject: Bug 549646 - NPE in ReferenceBinding.binarySearch Proper handling of ReferenceBindings that do have a null value in the sourceName field. Change-Id: I9bdb5c472553dc165b64665e4c262287dd1fa946 Signed-off-by: Sebastian Zarnekow Also-by: Stephan Herrmann --- .../jdt/core/tests/builder/Bug549646Test.java | 73 ++++++++++++++++++++++ .../jdt/core/tests/builder/BuilderTests.java | 1 + .../compiler/lookup/ProblemReferenceBinding.java | 3 + .../internal/compiler/lookup/ReferenceBinding.java | 34 +++++++--- 4 files changed, 104 insertions(+), 7 deletions(-) create mode 100644 org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug549646Test.java diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug549646Test.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug549646Test.java new file mode 100644 index 0000000000..f661339d08 --- /dev/null +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/Bug549646Test.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2019 Sebastian Zarnekow 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: + * Sebastian Zarnekow - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.core.tests.builder; + +import org.eclipse.core.runtime.IPath; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.tests.util.Util; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; + +import junit.framework.Test; + +public class Bug549646Test extends BuilderTests { + public Bug549646Test(String name) { + super(name); + } + + public static Test suite() { + return buildTestSuite(Bug549646Test.class); + } + + public void testCompilerRegression() throws JavaModelException, Exception { + IPath projectPath = env.addProject("Bug549646Test", "10"); //$NON-NLS-1$ + env.getJavaProject(projectPath).setOption(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED); + + env.addExternalJars(projectPath, Util.getJavaClassLibs()); + env.addClass(projectPath, "test", "A", //$NON-NLS-1$ //$NON-NLS-2$ + "package test;\n" + + "import java.util.Map;\n" + + "import java.util.Map.Entry;\n" + + "public class A {\n" + + " void a(Map a) {\n" + + " for (Entry iterableElement : a.entrySet()) {\n" + + " iterableElement.toString();\n" + + " }\n" + + " }\n" + + "}\n" //$NON-NLS-1$ + ); + env.addClass(projectPath, "test", "B", //$NON-NLS-1$ //$NON-NLS-2$ + "package test;\n" + + "import java.util.HashMap;\n" + + "public class B {\n" + + " void test() {\n" + + " new A().a(new HashMap());\n" + + " }\n" + + "}\n" //$NON-NLS-1$ + ); + fullBuild(); + + boolean isJRE11 = CompilerOptions.VERSION_11.equals(System.getProperty("java.specification.version")); + if (isJRE11 && env.getProblemsFor(projectPath).length > 0) { + // bogus class lookup (ignoring modules) due to insufficient data in ct.sym (non-deterministically triggers the below problems) + // see also https://bugs.eclipse.org/549647 + expectingProblemsFor(projectPath, + "Problem : Entry cannot be resolved to a type [ resource : range : <120,125> category : <40> severity : <2>]\n" + + "Problem : The type java.util.Map.Entry is not visible [ resource : range : <43,62> category : <40> severity : <2>]\n" + + "Problem : Type mismatch: cannot convert from element type Map.Entry to Entry [ resource : range : <160,172> category : <40> severity : <2>]"); + } else { + expectingNoProblems(); + } + } +} diff --git a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java index 3a79787fd8..cc1ecf9b36 100644 --- a/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java +++ b/org.eclipse.jdt.core.tests.builder/src/org/eclipse/jdt/core/tests/builder/BuilderTests.java @@ -562,6 +562,7 @@ public class BuilderTests extends TestCase { } if (matchesCompliance(F_9)) { list.add(LeakTestsAfter9.class); + list.add(Bug549646Test.class); } return list.toArray(new Class[0]); } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ProblemReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ProblemReferenceBinding.java index 51c920f155..08e7324679 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ProblemReferenceBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ProblemReferenceBinding.java @@ -28,6 +28,9 @@ public class ProblemReferenceBinding extends ReferenceBinding { public ProblemReferenceBinding(char[][] compoundName, ReferenceBinding closestMatch, int problemReason) { this.compoundName = compoundName; this.closestMatch = closestMatch; + if (closestMatch != null) { + this.sourceName = closestMatch.sourceName; + } this.problemReason = problemReason; } diff --git a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java index 5c1e31fcaa..537d99fbc3 100644 --- a/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java +++ b/org.eclipse.jdt.core/compiler/org/eclipse/jdt/internal/compiler/lookup/ReferenceBinding.java @@ -241,9 +241,23 @@ static void sortMemberTypes(ReferenceBinding[] sortedMemberTypes, int left, int Arrays.sort(sortedMemberTypes, left, right, BASIC_MEMBER_TYPES_COMPARATOR); } +/** + * Compares two reference bindings by the value of the {@link #sourceName} field. + * A ReferenceBinding with a sourceName field that has the value null is considered + * to be smaller than a ReferenceBinding that does have a source name. + */ static final Comparator BASIC_MEMBER_TYPES_COMPARATOR = (ReferenceBinding o1, ReferenceBinding o2) -> { char[] n1 = o1.sourceName; char[] n2 = o2.sourceName; + // n1 or n2 may be null - compare without accessing the length of the array + if (n1 == null) { + if (n2 == null) { + return 0; + } + return -1; + } else if (n2 == null) { + return 1; + } return ReferenceBinding.compare(n1, n2, n1.length, n2.length); }; @@ -1075,18 +1089,24 @@ public ReferenceBinding getMemberType(char[] typeName) { return null; } -static int binarySearch(char[] name, ReferenceBinding[] sortedMemberTypes) { +/** + * Search the given sourceName in the list of sorted member types. + * + * Neither the array of sortedMemberTypes nor the given sourceName may be null. + */ +static int binarySearch(char[] sourceName, ReferenceBinding[] sortedMemberTypes) { if (sortedMemberTypes == null) return -1; - int max = sortedMemberTypes.length; + int max = sortedMemberTypes.length, nameLength = sourceName.length; if (max == 0) return -1; - int left = 0, right = max - 1, nameLength = name.length; - int mid = 0; - char[] midName; + int left = 0, right = max - 1; while (left <= right) { - mid = left + (right - left) /2; - int compare = compare(name, midName = sortedMemberTypes[mid].sourceName, nameLength, midName.length); + int mid = left + (right - left) / 2; + char[] midName = sortedMemberTypes[mid].sourceName; + // The read source name may be null. In that case, the given sourceName is considered + // to be larger than the current value at mid. + int compare = midName == null ? 1 : compare(sourceName, midName, nameLength, midName.length); if (compare < 0) { right = mid-1; } else if (compare > 0) { -- cgit v1.2.3