1 package edu
.berkeley
.cs
.bodik
.svelte
.slicers
;
3 import java
.util
.ArrayList
;
4 import java
.util
.Collection
;
5 import java
.util
.Collections
;
6 import java
.util
.LinkedList
;
9 import org
.eclipse
.core
.resources
.IFile
;
10 import org
.eclipse
.core
.runtime
.CoreException
;
11 import org
.eclipse
.core
.runtime
.IProgressMonitor
;
12 import org
.eclipse
.core
.runtime
.IStatus
;
13 import org
.eclipse
.core
.runtime
.Status
;
14 import org
.eclipse
.debug
.core
.DebugPlugin
;
15 import org
.eclipse
.debug
.core
.ILaunchConfiguration
;
16 import org
.eclipse
.debug
.core
.ILaunchManager
;
17 import org
.eclipse
.jdt
.core
.ICompilationUnit
;
18 import org
.eclipse
.jdt
.core
.IJavaProject
;
19 import org
.eclipse
.jdt
.core
.IMethod
;
20 import org
.eclipse
.jdt
.core
.IType
;
21 import org
.eclipse
.jdt
.core
.JavaCore
;
22 import org
.eclipse
.jdt
.core
.dom
.ASTNode
;
23 import org
.eclipse
.jdt
.core
.dom
.ArrayAccess
;
24 import org
.eclipse
.jdt
.core
.dom
.ArrayCreation
;
25 import org
.eclipse
.jdt
.core
.dom
.ArrayInitializer
;
26 import org
.eclipse
.jdt
.core
.dom
.Assignment
;
27 import org
.eclipse
.jdt
.core
.dom
.Expression
;
28 import org
.eclipse
.jdt
.core
.dom
.FieldAccess
;
29 import org
.eclipse
.jdt
.core
.dom
.IMethodBinding
;
30 import org
.eclipse
.jdt
.core
.dom
.ITypeBinding
;
31 import org
.eclipse
.jdt
.core
.dom
.InfixExpression
;
32 import org
.eclipse
.jdt
.core
.dom
.MethodDeclaration
;
33 import org
.eclipse
.jdt
.core
.dom
.MethodInvocation
;
34 import org
.eclipse
.jdt
.core
.dom
.ParenthesizedExpression
;
35 import org
.eclipse
.jdt
.core
.dom
.QualifiedName
;
36 import org
.eclipse
.jdt
.core
.dom
.SingleVariableDeclaration
;
37 import org
.eclipse
.jface
.text
.BadLocationException
;
38 import org
.eclipse
.jface
.text
.IDocument
;
39 import org
.eclipse
.ui
.IEditorInput
;
40 import org
.eclipse
.ui
.IEditorPart
;
41 import org
.eclipse
.ui
.PlatformUI
;
42 import org
.eclipse
.ui
.progress
.UIJob
;
43 import org
.eclipse
.ui
.texteditor
.AbstractTextEditor
;
44 import org
.eclipse
.ui
.texteditor
.IDocumentProvider
;
45 import org
.eclipse
.ui
.texteditor
.ITextEditor
;
47 import com
.ibm
.wala
.ipa
.callgraph
.CGNode
;
48 import com
.ibm
.wala
.ipa
.callgraph
.CallGraph
;
49 import com
.ibm
.wala
.ipa
.callgraph
.propagation
.PointerAnalysis
;
50 import com
.ibm
.wala
.ipa
.slicer
.NormalReturnCaller
;
51 import com
.ibm
.wala
.ipa
.slicer
.NormalStatement
;
52 import com
.ibm
.wala
.ipa
.slicer
.ParamCallee
;
53 import com
.ibm
.wala
.ipa
.slicer
.Statement
;
54 import com
.ibm
.wala
.ssa
.SSAAbstractInvokeInstruction
;
55 import com
.ibm
.wala
.ssa
.SSAArrayStoreInstruction
;
56 import com
.ibm
.wala
.ssa
.SSAInstruction
;
57 import com
.ibm
.wala
.ssa
.SSAInvokeInstruction
;
58 import com
.ibm
.wala
.types
.TypeReference
;
60 import edu
.berkeley
.cs
.bodik
.svelte
.CGNodeUtils
;
61 import edu
.berkeley
.cs
.bodik
.svelte
.CallGraphUtils
;
62 import edu
.berkeley
.cs
.bodik
.svelte
.Slice
;
63 import edu
.berkeley
.cs
.bodik
.svelte
.Slicing
;
64 import edu
.berkeley
.cs
.bodik
.svelte
.SourcePosition
;
65 import edu
.berkeley
.cs
.bodik
.svelte
.SvelteAnalysisEngine
;
66 import edu
.berkeley
.cs
.bodik
.svelte
.Slicing
.SliceType
;
67 import edu
.berkeley
.cs
.bodik
.svelte
.plugin
.EclipseJdtUtils
;
68 import edu
.berkeley
.cs
.bodik
.svelte
.plugin
.EclipseUtils
;
69 import edu
.berkeley
.cs
.bodik
.svelte
.plugin
.SveltePlugin
;
72 * A slicer that handles Java code by using CAST's Java support
76 public class JavaLanguage
implements ILanguage
{
77 public static boolean useShrike
= false;
83 private class JavaSliceEnvironment
extends AbstractSliceEnvironment
{
84 private IJavaProject javaProject
;
85 private SvelteAnalysisEngine se
= new SvelteAnalysisEngine();
87 private JavaSliceEnvironment(IFile file
, long modTimeStamp
) {
88 super(file
, modTimeStamp
);
89 this.javaProject
= ((ICompilationUnit
) JavaCore
.create(file
)).getJavaProject();
91 // initialize the SvelteAnalysisEngine with appropriate entry points
92 this.se
.addEclipseClasspaths(this.javaProject
, useShrike
);
94 List
<String
> mainClasses
= findLauncherMainClasses(javaProject
);
96 // if no launchers for project, try open editor's className anyway.
97 // the user may get the
99 String fileClassname
= this.getClassName(file
);
100 if ((!mainClasses
.contains(fileClassname
)) && EclipseJdtUtils
.fileHasMain(file
))
101 mainClasses
.add(fileClassname
);
103 se
.findEntrypointFromClassnamesOfMain(mainClasses
);
107 * Retrieves the class name of a given file.
109 * Since this ignores position we will never get an inner class from this, so the code
110 * you're looking for may be in another class file.
113 * @return the fully qualified class name for the file
115 private String
getClassName(IFile file
) {
116 ICompilationUnit compilationUnit
= (ICompilationUnit
) JavaCore
.create(file
);
117 IType type
= compilationUnit
.findPrimaryType();
118 return type
.getFullyQualifiedName();
122 * If the SourcePosition selects a formal parameter in a method declaration, return the
123 * parameter index, starting at 0. If it finds a parameter, also (should but can't since we
124 * can't get the document in this thread) sets the line numbers of the beginning of the
125 * function to make sure we can find the method in WALA's CGNode. Also uses selectAndReveal
126 * to select the whole parameter (hack) if necessary
130 * @return Parameter index of -1 if position does not correspond to a selected or
131 * partially-selected parameter
133 private int getParameterIndex(SourcePosition position
, IEditorPart editor
) {
135 ICompilationUnit cu
= (ICompilationUnit
) JavaCore
.create(position
.getFile());
136 ASTNode root
= EclipseUtils
.getRootNode(cu
);
139 ASTNode covering
= EclipseUtils
.getCoveringNode(root
, position
.offsetStart
,
140 position
.offsetEnd
- position
.offsetStart
);
142 // ascend the tree until we find a method.
143 ASTNode iter
= covering
;
144 while (iter
!= null) {
145 if (iter
.getNodeType() == ASTNode
.SINGLE_VARIABLE_DECLARATION
) {
146 SingleVariableDeclaration svd
= (SingleVariableDeclaration
) iter
;
147 System
.out
.println("testing the singlevariabledeclaration");
148 System
.out
.println(svd
.getLocationInParent());
149 MethodDeclaration md
= (MethodDeclaration
) svd
.getParent();
151 // set position's line number
152 IDocumentProvider prov
= ((ITextEditor
) editor
).getDocumentProvider();
153 IEditorInput input
= editor
.getEditorInput();
154 IDocument document
= prov
.getDocument(input
);
156 position
.lineStart
= position
.lineEnd
= document
.getLineOfOffset(md
157 .getBody().getStartPosition()) + 1;
158 } catch (BadLocationException e
) {
162 selectAndRevealInUIThread(iter
.getStartPosition(), iter
.getLength());
163 return md
.parameters().indexOf(svd
);
165 iter
= iter
.getParent();
172 public CallGraph
getCallGraph() {
173 return se
.getCallGraph();
177 public PointerAnalysis
getPointerAnalysis() {
178 return se
.getPointerAnalysis();
182 * Look at the Java application launchers for a given project and determine all main
186 * @return List<String> where each element is the fully qualified name of each entrypoint
187 * listed in the project's launchers
189 public List
<String
> findLauncherMainClasses(IJavaProject project
) {
190 ILaunchManager manager
= DebugPlugin
.getDefault().getLaunchManager();
191 List
<String
> classes
= new LinkedList
<String
>();
193 ILaunchConfiguration configs
[] = manager
.getLaunchConfigurations();
194 for (ILaunchConfiguration cfg
: configs
) {
195 if (cfg
.getType().getIdentifier().equals(
196 "org.eclipse.jdt.launching.localJavaApplication")
197 && cfg
.getAttribute("org.eclipse.jdt.launching.PROJECT_ATTR", "")
198 .equals(project
.getElementName())) {
199 String classWithMain
= cfg
.getAttribute("org.eclipse.jdt.launching.MAIN_TYPE",(String
) null);
200 IType classWithMainType
= project
.findType(classWithMain
);
201 // check if it really has a main method. TODO: this only works for source. maybe take this out.
202 if ( classWithMainType
!= null ) {
203 for ( IMethod met
: classWithMainType
.getMethods() )
204 if ( met
.getElementName().equals("main") && met
.getParameterTypes().length
== 1
205 && (met
.getParameterTypes()[0].equals("[QString;")
206 || met
.getParameterTypes()[0].equals("[Qjava.lang.String;"))) {
207 classes
.add(classWithMain
);
213 } catch (CoreException e
) {
220 public Slice
getSlice(SliceType slicerType
, int depth
, Collection
<Statement
> seed
) {
222 if (seed
.size() == 0)
223 System
.err
.println("ERROR: slice is size 0");
225 Slice s
= Slicing
.doSlice(slicerType
, this, new Slice(seed
, this), depth
);
234 public Collection
<Statement
> generateSeed(SourcePosition position
, IEditorPart editor
) {
235 CallGraph cg
= getCallGraph();
236 IFile file
= position
.getFile();
239 EclipseUtils
.errorMsgInUIThread("Could not build call graph",
240 "Could not build call graph, probably due to an internal error. Check console output for more info.");
246 // check if we have selected a parameter.
247 // do this first, in changes position so we can find the method (if the declaration
248 // spans multiple lines)
249 int paramIndex
= getParameterIndex(position
, editor
);
250 int linenum
= position
.lineStart
; // selection.getStartLine() + 1;
252 Collection
<Statement
> result
;
255 // Shrike-loaded class don't have source file information. Compare based on
256 // fully-qualified class name.
257 // TODO: this doesn't find correct class name for an inner class.
258 // We have to use the JDT AST to see what the innermost class/interface is.
259 String mainClassName
= this.getClassName(file
);
261 cgn
= CallGraphUtils
.findMethodIncludingLineNumInClass(cg
, linenum
, mainClassName
);
263 cgn
= CallGraphUtils
.findMethodIncludingLineNum(cg
, linenum
, file
.getLocation()
268 EclipseUtils
.errorMsgInUIThread("Could not find method",
269 "Could not find a method containing the line number of code you have selected. It may be that:\n1) You have selected something outside of a method \n2) The source file is out-of-date and needs to be saved\n3) The method is not reachable from the program entrypoints");
273 if (paramIndex
!= -1) { // slice from formal parameter
274 int valueNumber
= paramIndex
+ 1; // paramIndex starts at zero
275 if (!cgn
.getMethod().isStatic())
276 valueNumber
++; // v1 = this
277 result
= Collections
.singleton((Statement
) new ParamCallee(cgn
, valueNumber
));
278 } else if (useShrike
) // all statements from line number
279 return CGNodeUtils
.getStatementsFromLineNumber(cgn
, linenum
);
280 else { // slice from selection
281 result
= reduceSubstatement(position
,cgn
);
282 if ( result
.size() > 0 ) {
283 Object first
= result
.iterator().next();
284 if ( first
instanceof NormalStatement
&& ((NormalStatement
)first
).getInstruction() == null ) {
285 EclipseUtils
.errorMsgInUIThread("Could not find seed",
286 "The statement you have selected as a seed is null in the intermediate representation. This probably means that the statement has been optimized away, as in the case of simple assignment statements (\"x=y\"). Please choose another statement where the value in the assignement statement originated from.");
293 // swap out invoke instructions for their return values
294 ArrayList
<Statement
> newSeed
= new ArrayList
<Statement
>();
295 for (Statement s
: result
)
296 if (s
instanceof NormalStatement
297 && ((NormalStatement
) s
).getInstruction() instanceof SSAAbstractInvokeInstruction
298 && ((SSAAbstractInvokeInstruction
) ((NormalStatement
) s
).getInstruction())
299 .getDeclaredResultType() != TypeReference
.Void
)
300 newSeed
.add(new NormalReturnCaller(s
.getNode(), ((NormalStatement
) s
)
301 .getInstructionIndex()));
305 if (result
== null || newSeed
.size() == 0 )
306 EclipseUtils
.errorMsgInUIThread("Could not find seed",
307 "Could not find a suitable seed to slice from. It may be that:\n1) You have selected something that evaluates to a constant\n2) The statement selected has been optimized out (i.e., simple assignment statements)\n3) The source file is out-of-date and needs to be saved");
313 * Some variables may not have statements at that line, e.g. assume foo, a, b, c, and d are
314 * locals: Then they will not have defining statements in 'foo.bar(a[b],c+d)', but their
315 * value numbers are used directly.
317 * Given a source position, if a simple name is selected, this function changes position
318 * to the the statement that uses the simple name and returns the index of the use.
320 * For example, let's say the user selects 'b' above: then position will be changed to the position
321 * of 'a[b]' and the return value will be 1 (i.e. we should look at use 1 in the WALA arrayload instruction).
323 * To accomplish this, we look at the context of the node referenced by position - i.e., the type of its parent node
324 * and its location in the parent node.
327 * This variable is changed to the match parent node --the instruction that we should find in the IR,
328 * using the return value as the index into the uses of this instruction
332 private Collection
<Statement
> reduceSubstatement(SourcePosition position
, CGNode cgn
) {
333 int substatementIndex
= -1;
334 Class
<?
extends SSAInstruction
> parentInstructionClass
= null;
337 ICompilationUnit cu
= (ICompilationUnit
) JavaCore
.create(position
.getFile());
338 ASTNode root
= EclipseUtils
.getRootNodeWithBindings(cu
);
341 ASTNode covering
= EclipseUtils
.getCoveringNode(root
, position
.offsetStart
,
342 position
.offsetEnd
- position
.offsetStart
);
344 // walk down any parentheses to get to the real expression to check if it is a
346 while (covering
.getNodeType() == ASTNode
.PARENTHESIZED_EXPRESSION
)
347 covering
= ((ParenthesizedExpression
) covering
).getExpression();
349 if (covering
.getNodeType() == ASTNode
.SIMPLE_NAME
) {
350 final ASTNode toSelect
= covering
;
352 // get rid of any parentheses above, so the next parent will be the actual parent.
353 // e.g. "(((foo))).bar" -> get to the outermost paren so parent will be
354 // qualifedexpression
355 while (covering
.getParent().getNodeType() == ASTNode
.PARENTHESIZED_EXPRESSION
)
356 covering
= covering
.getParent();
358 ASTNode parent
= covering
.getParent();
359 if (parent
.getNodeType() == ASTNode
.ARRAY_INITIALIZER
) {
360 ArrayInitializer ai
= (ArrayInitializer
) covering
.getParent();
361 substatementIndex
= 0;
362 boolean found
= false;
363 for ( Object o
: ai
.expressions() ) {
364 if ( o
.equals(covering
) ) {
371 return CGNodeUtils
.getNthArrayStoreStatementFromOffset(cgn
,
372 parent
.getStartPosition(), parent
.getStartPosition()+parent
.getLength(),
373 substatementIndex
, SSAArrayStoreInstruction
.class);
376 if (parent
.getNodeType() == ASTNode
.ARRAY_CREATION
) {
377 ArrayCreation ac
= (ArrayCreation
) covering
.getParent();
378 substatementIndex
= ac
.dimensions().indexOf(covering
);
379 if (substatementIndex
!= -1) {
380 position
.offsetStart
= ac
.getStartPosition();
381 position
.offsetEnd
= ac
.getStartPosition() + ac
.getLength();
384 else if (parent
.getNodeType() == ASTNode
.INFIX_EXPRESSION
) {
385 InfixExpression ie
= (InfixExpression
) covering
.getParent();
386 if ( covering
== ie
.getLeftOperand() ) {
387 substatementIndex
= 0;
388 // don't look at extended operands, if there are any.
389 // also, JDT conversion uses start of left operand, not start of infix expression (difference?)
390 position
.offsetStart
= ie
.getLeftOperand().getStartPosition();
391 position
.offsetEnd
= ie
.getRightOperand().getStartPosition() + ie
.getRightOperand().getLength();
392 } else if ( covering
== ie
.getRightOperand() ) {
393 substatementIndex
= 1;
394 position
.offsetStart
= ie
.getLeftOperand().getStartPosition();
395 position
.offsetEnd
= ie
.getRightOperand().getStartPosition() + ie
.getRightOperand().getLength();
398 substatementIndex
= 1; // it will always be the second operand, since we add operands to the right hand side
399 // the trick is determining the position: from the start of the first expression to the end of this expression
400 position
.offsetStart
= ie
.getLeftOperand().getStartPosition();
401 // position.offsetEnd same
403 } else if (parent
.getNodeType() == ASTNode
.METHOD_INVOCATION
) {
404 MethodInvocation mi
= (MethodInvocation
) covering
.getParent();
405 position
.offsetStart
= mi
.getStartPosition();
406 position
.offsetEnd
= mi
.getStartPosition() + mi
.getLength();
408 // if varargs, args are boxed first, so we have to deal with that.
409 IMethodBinding binding
= mi
.resolveMethodBinding().getMethodDeclaration();
410 if ( binding
.isVarargs() && mi
.getExpression() != covering
) {
411 List arguments
= mi
.arguments();
412 int nActuals
= arguments
.size();
413 int nFormals
= binding
.getParameterTypes().length
;
415 // if the nActuals != nFormals, it's definitely a varargs invocation. If
416 // nActuals == nFormals, however, we have to check the type of the last argument.
417 // if the last argument is an array type of the correct type, it will be passed in and not boxed.
418 ITypeBinding lastArgType
= null;
419 if ( nActuals
> 0 && arguments
.get(nActuals
-1) instanceof Expression
) // if nActuals=0 then it's definitely varargs and nFormals != 0
420 lastArgType
= ((Expression
)arguments
.get(nActuals
-1)).resolveTypeBinding();
422 if ( nActuals
!= nFormals
|| lastArgType
.isSubTypeCompatible(binding
.getParameterTypes()[nFormals
-1]) ) {
423 int argIndex
= arguments
.indexOf(covering
);
425 if ( argIndex
>= (nFormals
-1) ) {
426 int indexWithinVarargsArray
= argIndex
- (nFormals
-1);
427 return CGNodeUtils
.getNthArrayStoreStatementFromOffset(cgn
,
428 parent
.getStartPosition(), parent
.getStartPosition() + parent
.getLength(),
429 indexWithinVarargsArray
, SSAArrayStoreInstruction
.class);
430 // argIndex - nFormals is
433 // one of the previous, regular args in a varargs invocation.
434 // be very sure to look at invocation instructions only (don't look at the new/array store instructions before it)
437 parentInstructionClass
= SSAAbstractInvokeInstruction
.class;
439 if (mi
.getExpression() == covering
)
440 substatementIndex
= 0;
442 int argIndex
= mi
.arguments().indexOf(covering
);
443 if (argIndex
!= -1) {
444 substatementIndex
= argIndex
;
445 if (mi
.getExpression() != null)
446 substatementIndex
++; // result = argIndex + 1 "foo.bar(a,b,c)" -> a is index 1;
447 // "bar(a,b,c)" -> a is index 0
450 } else if (parent
.getNodeType() == ASTNode
.ARRAY_ACCESS
) {
451 ArrayAccess aa
= (ArrayAccess
) covering
.getParent();
452 if (parent
.getParent().getNodeType() == ASTNode
.ASSIGNMENT
) {
453 // case 1: "a[b] = c" -> WALA statement covers whole thing (putfield) EXCEPT
454 // PARENTHESES (due to WALA/polyglot bug)!
455 position
.offsetStart
= parent
.getParent().getStartPosition();
456 position
.offsetEnd
= parent
.getParent().getStartPosition()
457 + parent
.getParent().getLength();
459 // case 2: "a[b].c = d" (select a) or "a[b]"
461 position
.offsetStart
= parent
.getStartPosition();
462 position
.offsetEnd
= parent
.getStartPosition() + parent
.getLength();
465 if (aa
.getArray() == covering
)
466 substatementIndex
= 0;
467 if (aa
.getIndex() == covering
)
468 substatementIndex
= 1;
469 } else if (parent
.getNodeType() == ASTNode
.QUALIFIED_NAME
470 || parent
.getNodeType() == ASTNode
.FIELD_ACCESS
) {
471 // it will be a FIELD_ACCESS in the case if parentheses: (foo).bar = 5
472 // it will be a QUALIFIED_NAME otherwise: foo.bar = 5
473 ASTNode qualifier
= (parent
.getNodeType() == ASTNode
.QUALIFIED_NAME
) ?
((QualifiedName
) parent
)
475 : ((FieldAccess
) parent
).getExpression();
476 if (qualifier
== covering
) {
477 if (parent
.getParent().getNodeType() == ASTNode
.ASSIGNMENT
&&
478 ((Assignment
)parent
.getParent()).getLeftHandSide().equals(parent
) ) {
479 // case 1: "a.b = c" -> WALA statement covers whole thing (putfield)
480 // EXCEPT PARENTHESES (due to WALA/polyglot bug)!
481 position
.offsetStart
= parent
.getParent().getStartPosition();
482 position
.offsetEnd
= parent
.getParent().getStartPosition()
483 + parent
.getParent().getLength();
485 // case 2: "a.b.c = d" (select a) or "a.b" -- getfield, WALA statement
488 position
.offsetStart
= parent
.getStartPosition();
489 position
.offsetEnd
= parent
.getStartPosition() + parent
.getLength();
491 substatementIndex
= 0;
495 if (substatementIndex
!= -1) {
496 // selectAndReveal the actual simple name token to clarify what we are slicing
497 // on. this is kind of a hack
498 selectAndRevealInUIThread(toSelect
.getStartPosition(), toSelect
.getLength());
499 return CGNodeUtils
.getStatementFromOffsetAndSubstatementIndex(cgn
,
500 position
.offsetStart
, position
.offsetEnd
, substatementIndex
,
501 parentInstructionClass
);
508 return CGNodeUtils
.getInnermostStatementFromOffset(cgn
, position
.offsetStart
,
511 // System.out.println(covering.getParent().getNodeType());
513 // if ( covering.getNodeType() == ASTNode.)
514 // if covering node is a simple identifier
515 // check parent type:
516 // if the parent is a binop, getfield, putfield, invokevirtual, invokestatic, array
517 // load/store, new Bla[x][y],...
519 // it would be nice to add more safety here in case JDT AST & WALA statements don't
524 private void selectAndRevealInUIThread(final int startPosition
, final int length
) {
527 public IStatus
runInUIThread(IProgressMonitor monitor
) {
528 ((AbstractTextEditor
) (PlatformUI
.getWorkbench().getActiveWorkbenchWindow()
529 .getActivePage().getActiveEditor())).selectAndReveal(startPosition
,
531 return new Status(IStatus
.OK
, SveltePlugin
.PLUGIN_ID
, null);
537 public SourcePosition
statementToPosition(Statement statement
) {
538 return CGNodeUtils
.getSourcePositionOfInstructionIndex(statement
.getNode(),
539 ((NormalStatement
) statement
).getInstructionIndex(), this.javaProject
);
544 public AbstractSliceEnvironment
buildEnvironment(IFile file
, long modTimestamp
) {
545 return new JavaSliceEnvironment(file
, modTimestamp
);
549 * JavaSlicer can handle files with the .java extension
552 public boolean canSlice(IFile file
) {
553 return "java".equalsIgnoreCase(file
.getFileExtension());