Eclipse - Find Java references of a library without attached sources - java

Environment
I have a simple test-foo.jar library with just two files:
interface Foo with a single method void doStuff()
class Bar with a single method void executeFoo(Foo foo), which just calls foo.doStuff()
Then I have Eclipse Java project with a single class FooImpl which implements Foo. This project has test-foo.jar as a build path entry. I don't have source codes for this JAR attached.
The Problem
Now lets say I am a bit curious about who actually calls doStuff() method. So I click on the FooImpl#doStuff signature and press Ctrl+Shift+G (find references). I expect to see Bar#executeFoo in the search results, but the results are actually empty.
The Question
I am bit confused as I thought this works in Eclipse (been using Eclipse for more than a few years now). But it seems it only works for libraries with sources attached (which I didn't notice before).
Is there a way how to find references (or usage) of a type (or method) in project libraries without sources attached? Why does not Eclipse index and show references from .class files?
Additional info:
Finding references works as soon as I attach source codes to the JAR.
I am interested in knowing why Eclipse JDT developers decided to not index or search .class without sources.
Note, that knowing who is calling my method is very useful information even if I don't have source codes.
Seems to me that Eclipse is indexing method references in .class files.
And by the way NetBeans "don't work" as well ;).

You need the source attached to your library. There's no point for Eclipse to take you to the .class file since all you would see is a bunch of compiled jamble-wamble (that's just a word I came up with). Eclipse will only navigate to Java source files.

Related

Files with the .SCL.lombok extension

When the lombok jar file is opened in Intellij, all files other than the annotations end with .SCL.lombok (e.g. HandleAccessors.SCL.lombok). I was just wondering what the reason for this was and how it's handled.
The reason for it
Lombok has a public API - the stuff you're supposed to interact with. That'd be, for example, the #lombok.Getter annotation. Those are just class files in that jar, the aim is simply: add that jar to your classpath and your IDE autocomplete dialogs and the like will automatically start suggesting these, as per design.
But, lombok also has lots of classes that just 'make it tick', these aren't meant for public consumption. Things like lombok.eclipse.HandleGetter, which is the implementation for handling the #Getter annotation inside the eclipse agent. There is no point or purpose to referring to this class anywhere, in any project - it's an internal lombok thing. If we just stuck that jar file into the jar, and you typed Handle and hit your IDE's autocomplete shortcut key, you'd still get the suggestion.
Similarly, we ship a few dependencies straight into lombok.jar - it's a 'shaded jar' (a jar with all deps included), though we don't have many, keeping lombok.jar a nice small size. Still, ASM (a bytecode manipulation library) is in it, and that is fairly popular.
The standard shading solution offered by most shading tools is to prefix something to the name. ASM's org.objectweb.asm.AnnotationVisitor class would become org.projectlombok.shading.org.objectweb.asm.AnnotationVisitor. Point is, your IDE doesn't know that, and if you ALSO use asm in your project (where you also use lombok), and you want AnnotationVisitor thus you type AnnV and hit cmd+space or whatnot, your IDE suggests both. That's ugly and we'd like to avoid this.
Hence, we built our own shader, and it works by not having class files in the first place. This way, IDEs and any other automated tool doesn't even know either our ASM classes, or our implementation details, even exists. The only files that such tools (such as your IDE) sees are the types you're meant to see: lombok.Builder, lombok.extern.slf4j.Slf4j, lombok.experimental.UtilityClass, etcetera.
How does it work
Java's classloader architecture is abstracted: You can make your own. The primitives offered by a class loader is simply this: "Convert this byte array containing bytecode (i.e. the contents of a class file) into a Class<?> definition", and the primitives that you're supposed to implement when you write your own classloader is twofold:
Here is a resource key, such as "/com/foo/load.png". Please provide me an InputStream with this data.
Here is a fully qualified class name, such as "com.foo.MyApp". Please provide me with a Class<?> instance representing it.
Out of the box, java ships with a default classloader. This default classloader answers these questions by checking your CLASSPATH - which can be provided in various ways (via the jar manifest's Class-Path entry, or via the -cp argument to the JVM executable, or the CLASSPATH environment variable), and scanning each entry on the classpath for the resource requested, capable of reading the file system as well as opening jar files.
But that's just a classloader. One implementation of the general principle that's baked into java. You can write your own. You can write a classloader that generates resources on the fly, or that loads them from a network.
Or, as lombok does, that loads them by opening its own jar and looking for .SCL.lombok files.
Thus, lombok works like this: When you launch it, the 'entrypoint' (the class containing public static void main - or in lombok's case, for javac mode it's the annotation processor entrypoint and for eclipse it's agentmain), we 'hide' it from you using some fancy trickery: agentmain does not need to be in a public class (it can't be .SCL.lombok files - our classloader isn't available yet, we need to bootstrap that up first!). annotation processors do have to be in a public class, but, it's a public class inside a package private class, thus, just about every IDE knows it's 'invisible' and won't show it, but javac's annotation runner accepts it.
From there, we register a classloader that is capable of loading classes by way of reading in an .SCL.lombok file, and this lets us hide everything else we want to hide.
I want to develop lombok and this is getting in the way!
No need; just clone our repo, run ant eclipse or ant intellij, and off you go. There is no way to extend lombok without first forking it; we'd like lombok to be able to be extensible without it, but that would be far more complicated than simply not doing the .SCL.lombok thing. Eclipse runs on top of equinox, a runtime modularization system, and making that work properly requires all sorts of stuff that would make 'just toss some extra handlers on the classpath' not a feasible route to extending lombok in the first place.

Reverse Engineering of an APK

For my master thesis I'm studying and improving the security of an application. The code is obfuscated and my goal now is to see how easily I can exploit the application. However, I'm having some problems; I think I may have found a piece of code that, when removed, will allow me to override some fundamental step of the application logic. As such, I want to recompile the single class that contains that piece of code and replace the one on the application with this new version. I used dex2jar to obtain a jar with all the classes and have already obtained the .java file with the class I want to alter. My exact problem is how I can recompile the file. I already downloaded the Android API jars and I'm using javac with the classpath pointing to the jar having the remaining classes of the application and to the Android API, but I still can't compile as javac complains about some Android literals being ambiguous. Can you please help? Thanks!

Where does IntelliJ put .class files when it compiles during typing

Is IntelliJ compiling all the time since it tells me with red squiggly lines when there is an error? (in addition to the autocomplete features) Or is it doing some sort of psuedo compiling?
If it is doing legit compiling, where does it put these compiled classes? I'de like to point my JRebel to that directory instead of the individual module target folders.
Meo is right, from what I learned when I developed plugins for custom languages, IntelliJ does not compile anything until you explicitly make your project. While you are typing, its lexer/parser detects any invalid token or code construct. In the meantime, it maintains an index of every class and method in your project and its dependencies, along with their signature, etc.
After you stop typing, you'll see a little colored eye in the top part of the right gutter. It indicates that the IDE is running "annotators" and "code inspections". They are able to tell whether or not classes, methods and variable are valid based on the current index and the current state of your file (imports, declarations, etc.). The same goes for unused variables, invalid parameters in method calls, etc.
Pros:
annotators work directly on what they call a PSI tree, which is basically an enhanced AST representing your current file
it may be faster that compiling every time (it uses an index and does not need to recompile every dependent class)
annotators can detect things javac don't care about, such as potential bugs (e.g. using = instead of == in a while condition)
Cons:
that's a loooot of work, basically they need to rewrite the logic to find every error that javac can produce (which is why you can find many issues on their bugtracker labelled "good code is red" or "bad code is green", meaning there is a difference between what they detect and what the compiler would output)
TL;DR: it does not produce any .class until you make your project, everything is done "by hand"
For every module, the compiler output path can be found from Paths tab in Module Settings.
JRebel plugin generates rebel.xml automatically and derives the directory path from Module Settings, so you do not need to point to the locations manually - just generate rebel.xml using the IDE plugin: right click on module in the project view -> JRebel -> generate rebel.xml
Just to add, after compilation, the classes are stored in the target directory if it's a Maven project - otherwise, the directory is specified in IntelliJ's Project Structure, in "Project compiler output":
IntelliJ understands the code, it does not need to compile the code to know what is wrong.
I found my .class files by going to the out/production/main folders from the home directory of the project.

Java - how to get dependencies to ignore each other?

We are developing a fairly large project and have many dependencies. Recently, we ran into an issue with a conflict between two of them, agileAPI.jar and axis.jar. Both are 3rd party libraries.
The code in question depends directly on agileAPI.jar. If I build it with just that in the build path, everything that depends on it works correctly.
As soon as I add axis.jar to the build path (just adding it, not writing code that depends on it), everything goes wrong. Some of the code that depended on the first library is now throwing exceptions from the 2nd library. It is as if the first library is picking and choosing methods to call from the 2nd library, instead of whereever it was calling them from prior.
I have code in the project that needs axis.jar directly, so I can't just remove it from the build path. I need to find a way to have these two exist in the same build path, but ignore each other.
It should be noted that both libraries coexisted prior to a recent upgrade with agile. I have been working with Oracle's support team to try and resolve this. After two weeks, though, I am looking for other sources of help.
Our environment is Windows and Eclipse, although in testing this, it also occurs when running java from a command line. Our JDK is 1.5.0_22.
Any help would be appreciated.
Thank you,
David
EDIT:
As requested, here are the stack traces that we see. The first stack trace is printed in the code beyond my control:
java.lang.NoSuchMethodError: org.apache.axis.description.OperationDesc.setStyle(Lorg/apache/axis/constants/Style;)V
at com.agile.webfs.components.fileserver.client.FileServerSoapBindingStub._initOperationDesc1(FileServerSoapBindingStub.java:37)
at com.agile.webfs.components.fileserver.client.FileServerSoapBindingStub.<clinit>(FileServerSoapBindingStub.java:20)
at com.agile.webfs.components.fileserver.client.FileServerWSServiceLocator.getFileServer(FileServerWSServiceLocator.java:43)
at com.agile.webfs.client.IFSLocator.getRemoteFileServer(IFSLocator.java:128)
at com.agile.webfs.client.IFSLocator.getConnection(IFSLocator.java:101)
at com.agile.api.pc.EJBLookup.createFileSession(EJBLookup.java:444)
at com.agile.api.pc.EJBLookup.getFileSession(EJBLookup.java:432)
at com.agile.api.pc.attachment.IFSOutputStream.getFileSession(IFSOutputStream.java:133)
at com.agile.api.pc.attachment.IFSOutputStream.copyFrom(IFSOutputStream.java:87)
at com.agile.api.pc.attachment.IFSOutputStream.copyFrom(IFSOutputStream.java:115)
at com.agile.api.pc.TableAttachment.uploadFile(TableAttachment.java:886)
at com.agile.api.pc.TableAttachment$AddFiles2Action.doSdkAction(TableAttachment.java:724)
at com.agile.api.common.SDKAction.run(SDKAction.java:23)
at com.agile.api.common.OracleAuthenticator.doAs(OracleAuthenticator.java:131)
at com.agile.api.common.Security.doAs(Security.java:54)
at com.agile.api.common.Security.doAs(Security.java:109)
at com.agile.api.pc.TableAttachment.addFiles2(TableAttachment.java:483)
at com.agile.api.pc.TableAttachment.createNewBlob2(TableAttachment.java:459)
at com.agile.api.pc.TableAttachment.doCreateServerRowWithParam(TableAttachment.java:363)
at com.agile.api.pc.Table.createTableRow(Table.java:238)
at com.agile.api.pc.TableAttachment.createTableRow(TableAttachment.java:169)
at com.agile.api.pc.Table.createRow(Table.java:202)
at com.[snip].updateAttachments(VaultImportService.java:3068)
at com.[snip].processIncorporatedFile(VaultImportService.java:926)
at com.[snip].processPdxFile(VaultImportService.java:532)
at com.[snip].processPdxRequest(VaultImportService.java:388)
at com.[snip].<init>(VaultImportService.java:299)
at com.[snip].main(VaultImportService.java:3660)
After the exception bubbles up and we catch it, the stacktrace that we print looks like:
at com.agile.api.pc.Session.createError(Session.java:1772)
at com.agile.api.pc.EJBLookup.createFileSession(EJBLookup.java:454)
at com.agile.api.pc.EJBLookup.getFileSession(EJBLookup.java:432)
at com.agile.api.pc.attachment.IFSOutputStream.getFileSession(IFSOutputStream.java:133)
at com.agile.api.pc.attachment.IFSOutputStream.copyFrom(IFSOutputStream.java:87)
at com.agile.api.pc.attachment.IFSOutputStream.copyFrom(IFSOutputStream.java:115)
at com.agile.api.pc.TableAttachment.uploadFile(TableAttachment.java:886)
at com.agile.api.pc.TableAttachment$AddFiles2Action.doSdkAction(TableAttachment.java:724)
at com.agile.api.common.SDKAction.run(SDKAction.java:23)
at com.agile.api.common.OracleAuthenticator.doAs(OracleAuthenticator.java:131)
at com.agile.api.common.Security.doAs(Security.java:54)
at com.agile.api.common.Security.doAs(Security.java:109)
at com.agile.api.pc.TableAttachment.addFiles2(TableAttachment.java:483)
at com.agile.api.pc.TableAttachment.createNewBlob2(TableAttachment.java:459)
at com.agile.api.pc.TableAttachment.doCreateServerRowWithParam(TableAttachment.java:363)
at com.agile.api.pc.Table.createTableRow(Table.java:238)
at com.agile.api.pc.TableAttachment.createTableRow(TableAttachment.java:169)
at com.agile.api.pc.Table.createRow(Table.java:202)
at com.[snip].updateAttachments(VaultImportService.java:3068)
at com.[snip].processIncorporatedFile(VaultImportService.java:926)
at com.[snip].processPdxFile(VaultImportService.java:532)
at com.[snip].processPdxRequest(VaultImportService.java:388)
at com.[snip].<init>(VaultImportService.java:299)
at com.[snip].main(VaultImportService.java:3660)
In both cases, the line "at com.agile.api.pc.Table.createRow(Table.java:202)" is the agileAPI call that I am making. I have removed our package structure, as it identifies the company that I work for. They value privacy and security.
I'd advise you to check these two things first:
Open the axis.jar file with some zip utility, like 7-Zip or WinRar. See if there's a folder called "services" in the META-INF folder in the jar. If there is, it's possible that the axis.jar file specifies implementations for specific interfaces that somehow don't interoperate with agileAPI. Also do the same for agileAPI.jar, since it might itself declare an interface implementation that axis doesn't like.
Open both agileAPI.jar and axis.jar with a zip utility, then check if there's packages with the same name. If there's none, it won't be a naming conflict. If there's one or more, open the corresponding folders and do the same check recursively. If you end up with at least one class with the same name in the same package across the two jars, it's probably a naming conflict.
That should catch the most obvious issues. If none of this is the case, we'll need to look deeper.
A way to solve such classpath issues is to use a module system such as OSGi or the NetBeans Platform module system where each module has its own classloader.

How to find main() function in a big project

I never used Java, but I'm looking over a big server project writting in Java with Eclipse.
My question is, how can I find the main() function, is there an easy way, or I have to search for it in every .java file ?
You will have to do a code base wide search as it could be in any file. Having said that, many server based project do not have a main() function at all. The server provides the infrastructure and only looks for classes which inherit from specific other classes or implement certain interfaces or are mentioned in certain config files. All of this depends on the server and the technologies employed.
You need to find out what your server is, what technologies have been used in the java project and how it was setup.
Use eclipse's build in search function and search for "main(" in all projects java files (= entire workspace)
Look for the application jar and look at it's manifest file, it may contain the name of the main class
Look for scripts that are used to start the application. You eventually find the java call that starts the application (a parameter is the main class)
Look for build scripts (build.xml). Eventually they contain some jar, install or deploy target where startup scripts are autogenerated or manifest files are written. The main class should be named there.
BTW - If the big server project is a server based application, say the final build result is a war or an ear file, then the entry point doesn't have to be a static main method. Then it might not even have one single starting point.
Well, if nothing else, you could use the Search menu, Java, and use "main(String[])" as the search string. Search for: Method, Limit To: Declarations and Search In: Sources. That will help your search.
If you have a run configuration in Eclipse, you can look in that and see which class and method is being invoked.
In your project you may have more than one main() method. So, you should search it in files. Just find main which is declared as public static void.
If you use an ECLIPSE you can try to search with special ECLIPSE search features (menu Search -> Java...).
You better know where it is, because with Java, you can have several main() for one project.
I sometimes have one that runs the project as an application (standalone) and another that runs it as an applet.
The best practice is: know where it is, put it in an obvious place (like in the Main.java file).
Under eclipse Package Explorer right click on project name -> Run As -> Java Application
Eclipse will automatically search and execute main method i.e. public static void main(String[] args) {...}

Categories