Bug 334119

Summary: AIOOBE in BindingKeyParser.parseInnerType (was: Copy Qualified Name throws ArrayIndexOutOfBoundsException)
Product: [Eclipse Project] JDT Reporter: Dani Megert <daniel_megert>
Component: CoreAssignee: Ayushman Jain <amj87.iitr>
Status: VERIFIED FIXED QA Contact:
Severity: normal    
Priority: P3 CC: amj87.iitr, deepakazad, stephan.herrmann
Version: 3.7   
Target Milestone: 3.7 M5   
Hardware: PC   
OS: Windows XP   
Whiteboard:
Attachments:
Description Flags
proposed fix
none
proposed fix + regression tests none

Description Dani Megert CLA 2011-01-12 10:20:22 EST
I20110111-0800.

Copy From Clipboard throws ArrayIndexOutOfBoundsException.

1. paste this:
package pp;
public class A$ {
	void a() {
	}
}
2. select "a" in the editor
3. Copy > Copy Qualified Name
STRANGE: sometimes nothing at all happens
4. Copy > Copy Qualified Name
still no feedback but:
!ENTRY org.eclipse.ui 4 0 2011-01-12 15:53:48.908
!MESSAGE Unhandled event loop exception
!STACK 0
java.lang.ArrayIndexOutOfBoundsException: 0
	at org.eclipse.jdt.internal.core.util.BindingKeyParser.parseInnerType(BindingKeyParser.java:734)
	at org.eclipse.jdt.internal.core.util.BindingKeyParser.parse(BindingKeyParser.java:608)
	at org.eclipse.jdt.internal.core.util.BindingKeyParser.parse(BindingKeyParser.java:590)
	at org.eclipse.jdt.core.BindingKey.toSignature(BindingKey.java:305)
	at org.eclipse.jdt.internal.ui.viewsupport.JavaElementLabelComposer.appendMethodLabel(JavaElementLabelComposer.java:322)
	at org.eclipse.jdt.internal.ui.viewsupport.JavaElementLabelComposer.appendElementLabel(JavaElementLabelComposer.java:257)
	at org.eclipse.jdt.ui.JavaElementLabels.getElementLabel(JavaElementLabels.java:510)
	at org.eclipse.jdt.ui.JavaElementLabels.getElementLabel(JavaElementLabels.java:483)
	at org.eclipse.jdt.ui.JavaElementLabels.getTextLabel(JavaElementLabels.java:387)
	at org.eclipse.jdt.internal.ui.actions.CopyQualifiedNameAction.getQualifiedName(CopyQualifiedNameAction.java:261)
	at org.eclipse.jdt.internal.ui.actions.CopyQualifiedNameAction.run(CopyQualifiedNameAction.java:193)
	at org.eclipse.jface.action.Action.runWithEvent(Action.java:498)
	at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:584)
	at org.eclipse.jface.action.ActionContributionItem.access$2(ActionContributionItem.java:501)
	at org.eclipse.jface.action.ActionContributionItem$5.handleEvent(ActionContributionItem.java:411)
	at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
	at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1053)
	at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4089)
	at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3680)
	at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2697)
	at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2661)
	at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2495)
	at org.eclipse.ui.internal.Workbench$7.run(Workbench.java:674)
	at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
	at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:667)
	at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149)
	at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:115)
	at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:344)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:592)
	at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:622)
	at org.eclipse.equinox.launcher.Main.basicRun(Main.java:577)
	at org.eclipse.equinox.launcher.Main.run(Main.java:1410)
	at org.eclipse.equinox.launcher.Main.main(Main.java:1386)

!ENTRY org.eclipse.ui 4 0 2011-01-12 15:53:57.346
!MESSAGE Unhandled event loop exception
!STACK 0
java.lang.ArrayIndexOutOfBoundsException: 0
	at org.eclipse.jdt.internal.core.util.BindingKeyParser.parseInnerType(BindingKeyParser.java:734)
	at org.eclipse.jdt.internal.core.util.BindingKeyParser.parse(BindingKeyParser.java:608)
	at org.eclipse.jdt.internal.core.util.BindingKeyParser.parse(BindingKeyParser.java:590)
	at org.eclipse.jdt.core.BindingKey.isParameterizedType(BindingKey.java:279)
	at org.eclipse.jdt.internal.ui.viewsupport.JavaElementLabelComposer.appendTypeLabel(JavaElementLabelComposer.java:855)
	at org.eclipse.jdt.internal.ui.viewsupport.JavaElementLabelComposer.appendElementLabel(JavaElementLabelComposer.java:272)
	at org.eclipse.jdt.ui.JavaElementLabels.getElementLabel(JavaElementLabels.java:510)
	at org.eclipse.jdt.ui.JavaElementLabels.getElementLabel(JavaElementLabels.java:483)
	at org.eclipse.jdt.ui.JavaElementLabels.getTextLabel(JavaElementLabels.java:387)
	at org.eclipse.jdt.internal.ui.actions.CopyQualifiedNameAction.getQualifiedName(CopyQualifiedNameAction.java:261)
	at org.eclipse.jdt.internal.ui.actions.CopyQualifiedNameAction.run(CopyQualifiedNameAction.java:193)
	at org.eclipse.jface.action.Action.runWithEvent(Action.java:498)
	at org.eclipse.jface.action.ActionContributionItem.handleWidgetSelection(ActionContributionItem.java:584)
	at org.eclipse.jface.action.ActionContributionItem.access$2(ActionContributionItem.java:501)
	at org.eclipse.jface.action.ActionContributionItem$5.handleEvent(ActionContributionItem.java:411)
	at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:84)
	at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:1053)
	at org.eclipse.swt.widgets.Display.runDeferredEvents(Display.java:4089)
	at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:3680)
	at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2697)
	at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2661)
	at org.eclipse.ui.internal.Workbench.access$4(Workbench.java:2495)
	at org.eclipse.ui.internal.Workbench$7.run(Workbench.java:674)
	at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:332)
	at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:667)
	at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:149)
	at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:115)
	at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
	at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:344)
	at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
	at java.lang.reflect.Method.invoke(Method.java:592)
	at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:622)
	at org.eclipse.equinox.launcher.Main.basicRun(Main.java:577)
	at org.eclipse.equinox.launcher.Main.run(Main.java:1410)
	at org.eclipse.equinox.launcher.Main.main(Main.java:1386)
Comment 1 Ayushman Jain CLA 2011-01-13 02:08:05 EST
The problem is that JDT/Core assumes everywhere that a $ in the binding key would imply an innner type, and so a binding key such as A$B is always treated as type B enclosed by type A. Because of this whenever there's actually a $ in the type name, we split the type name at $. 
We should ideally change the representation of binding key for type names containing $ by inserting some escape character (preferably '\'), but this might break a lot of existing things which wouldnt expect a '\' in the binding key.

Specifically in this bug, the problem starts at SourceTypeBinding.computeUniqueKey(boolean). Since here we have the binding we can always query it for binding.isMemberType() and treat the dollar correctly, but then again in BindingKeyParser$Scanner.getNextToken(), we fumble on the dollar!
Comment 2 Ayushman Jain CLA 2011-01-13 02:17:23 EST
(In reply to comment #1)
Just an aferthought, why should java allow specifying a '$' in the name at all. I wish this could be changed. There are other currencies one can use. ;)
Comment 3 Dani Megert CLA 2011-01-13 03:34:10 EST
(In reply to comment #2)
> (In reply to comment #1)
> Just an aferthought, why should java allow specifying a '$' in the name at all.
> I wish this could be changed. There are other currencies one can use. ;)

The name is indeed discouraged (see new wizard).
Comment 4 Deepak Azad CLA 2011-01-13 04:46:53 EST
An extreme case

--------------------------------
class $ {
	class $$ extends ${
		class $$$ {
			
		}
	}
}
-------------------------------

Try Copy qualified name, Type Hierarchy on '$$' etc :)
Comment 5 Stephan Herrmann CLA 2011-01-13 05:46:16 EST
(In reply to comment #2)
> (In reply to comment #1)
> Just an aferthought, why should java allow specifying a '$' in the name at all.
> I wish this could be changed. There are other currencies one can use. ;)

For Java, '$' is just a normal char, it's the encoding of inner types
that's a major PITA introduced in Java 1.1.

Some related bugs: bug 127739, bug 127749, bug 145598.
We'll be adding workarounds for the rest of our lives :-/
Note that bug 127749 isn't resolved as of today.
Comment 6 Ayushman Jain CLA 2011-01-13 06:50:30 EST
Created attachment 186715 [details]
proposed fix

This patch takes care of all the above cases by making sure that the generated key from SourceTypeBinding.getUniqueKey() isn't bad in case of $ occuring in the name.

But this isn't fullproof. There can be other places that still break because of '$'. Like Stephan said, we could be coming across this forever, unless we add an escape character to the binding key. But since this is exposed to the outside world, doing so would break client code.
Comment 7 Ayushman Jain CLA 2011-01-13 07:10:39 EST
(In reply to comment #6)
> But this isn't fullproof.

Even with the patch, the following still throws an exception:

class A$$ {
    void a() {
   }
}

at a completely different place:
java.lang.IllegalArgumentException
	at org.eclipse.jdt.core.Signature.getParameterCount(Signature.java:995)
Comment 8 Ayushman Jain CLA 2011-01-20 04:43:26 EST
Created attachment 187170 [details]
proposed fix + regression tests

Same patch with tests.
Comment 9 Ayushman Jain CLA 2011-01-21 02:17:46 EST
Released in HEAD for 3.7M5
Comment 10 Ayushman Jain CLA 2011-01-21 02:18:18 EST
To verify, use comment 0 and comment 4
Comment 11 Dani Megert CLA 2011-01-25 04:04:32 EST
Verified in I20110124-1800.