1 /*******************************************************************************
2  * Copyright (c) 2000, 2020 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Dmitry Stalnov (dstalnov@fusionone.com) - contributed fixes for:
14  *       o bug "inline method - doesn't handle implicit cast" (see
15  *         https://bugs.eclipse.org/bugs/show_bug.cgi?id=24941).
16  *       o bug inline method: compile error (array related)
17  *         (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38471)
18  *       o inline call that is used in a field initializer
19  *         (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38137)
20  *       o inline call a field initializer: could detect self reference
21  *         (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=44417)
22  *       o Allow 'this' constructor to be inlined
23  *         (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=38093)
24  *     Nikolay Metchev <nikolaymetchev@gmail.com> - Anonymous class using final parameter breaks method inlining - https://bugs.eclipse.org/269401
25  *     Microsoft Corporation - copied to jdt.core.manipulation
26  *     Pierre-Yves B. <pyvesdev@gmail.com> - [inline] Inlining a local variable leads to ambiguity with overloaded methods - https://bugs.eclipse.org/434747
27  *******************************************************************************/
28 package org.eclipse.jdt.internal.corext.refactoring.code;
29 
30 import java.util.ArrayList;
31 import java.util.HashSet;
32 import java.util.List;
33 import java.util.Set;
34 
35 import org.eclipse.core.runtime.Assert;
36 import org.eclipse.core.runtime.CoreException;
37 
38 import org.eclipse.core.filebuffers.ITextFileBuffer;
39 
40 import org.eclipse.text.edits.TextEdit;
41 import org.eclipse.text.edits.TextEditGroup;
42 
43 import org.eclipse.jface.text.BadLocationException;
44 
45 import org.eclipse.ltk.core.refactoring.RefactoringStatus;
46 import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
47 
48 import org.eclipse.jdt.core.ICompilationUnit;
49 import org.eclipse.jdt.core.dom.AST;
50 import org.eclipse.jdt.core.dom.ASTNode;
51 import org.eclipse.jdt.core.dom.ASTVisitor;
52 import org.eclipse.jdt.core.dom.ArrayInitializer;
53 import org.eclipse.jdt.core.dom.Assignment;
54 import org.eclipse.jdt.core.dom.Block;
55 import org.eclipse.jdt.core.dom.BodyDeclaration;
56 import org.eclipse.jdt.core.dom.CastExpression;
57 import org.eclipse.jdt.core.dom.CompilationUnit;
58 import org.eclipse.jdt.core.dom.DoStatement;
59 import org.eclipse.jdt.core.dom.EnhancedForStatement;
60 import org.eclipse.jdt.core.dom.Expression;
61 import org.eclipse.jdt.core.dom.FieldAccess;
62 import org.eclipse.jdt.core.dom.FieldDeclaration;
63 import org.eclipse.jdt.core.dom.ForStatement;
64 import org.eclipse.jdt.core.dom.IBinding;
65 import org.eclipse.jdt.core.dom.IMethodBinding;
66 import org.eclipse.jdt.core.dom.ITypeBinding;
67 import org.eclipse.jdt.core.dom.IVariableBinding;
68 import org.eclipse.jdt.core.dom.IfStatement;
69 import org.eclipse.jdt.core.dom.LabeledStatement;
70 import org.eclipse.jdt.core.dom.MethodDeclaration;
71 import org.eclipse.jdt.core.dom.MethodInvocation;
72 import org.eclipse.jdt.core.dom.Modifier;
73 import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
74 import org.eclipse.jdt.core.dom.Name;
75 import org.eclipse.jdt.core.dom.ParenthesizedExpression;
76 import org.eclipse.jdt.core.dom.ReturnStatement;
77 import org.eclipse.jdt.core.dom.SimpleName;
78 import org.eclipse.jdt.core.dom.Statement;
79 import org.eclipse.jdt.core.dom.SuperFieldAccess;
80 import org.eclipse.jdt.core.dom.SwitchStatement;
81 import org.eclipse.jdt.core.dom.ThisExpression;
82 import org.eclipse.jdt.core.dom.Type;
83 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
84 import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
85 import org.eclipse.jdt.core.dom.WhileStatement;
86 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
87 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
88 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
89 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
90 
91 import org.eclipse.jdt.internal.core.manipulation.JavaManipulationPlugin;
92 import org.eclipse.jdt.internal.core.manipulation.StubUtility;
93 import org.eclipse.jdt.internal.core.manipulation.dom.NecessaryParenthesesChecker;
94 import org.eclipse.jdt.internal.corext.CorextCore;
95 import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
96 import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
97 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
98 import org.eclipse.jdt.internal.corext.dom.CodeScopeBuilder;
99 import org.eclipse.jdt.internal.corext.dom.HierarchicalASTVisitor;
100 import org.eclipse.jdt.internal.corext.dom.LocalVariableIndex;
101 import org.eclipse.jdt.internal.corext.dom.Selection;
102 import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
103 import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes;
104 import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowContext;
105 import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowInfo;
106 import org.eclipse.jdt.internal.corext.refactoring.code.flow.InputFlowAnalyzer;
107 import org.eclipse.jdt.internal.corext.refactoring.typeconstraints.types.TypeEnvironment;
108 import org.eclipse.jdt.internal.corext.refactoring.util.JavaStatusContext;
109 import org.eclipse.jdt.internal.corext.refactoring.util.NoCommentSourceRangeComputer;
110 import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringFileBuffers;
111 
112 public class CallInliner {
113 
114 	private ICompilationUnit fCUnit;
115 	private ASTRewrite fRewrite;
116 	private ImportRewrite fImportRewrite;
117 	private ITextFileBuffer fBuffer;
118 	private SourceProvider fSourceProvider;
119 	private TypeEnvironment fTypeEnvironment;
120 
121 	private BodyDeclaration fBodyDeclaration;
122 	private CodeScopeBuilder.Scope fRootScope;
123 	private int fNumberOfLocals;
124 
125 	private ASTNode fInvocation;
126 
127 	private int fInsertionIndex;
128 	private ListRewrite fListRewrite;
129 
130 	private boolean fNeedsStatement;
131 	private ASTNode fTargetNode;
132 	private FlowContext fFlowContext;
133 	private FlowInfo fFlowInfo;
134 	private CodeScopeBuilder.Scope fInvocationScope;
135 	private boolean fFieldInitializer;
136 	private List<VariableDeclarationStatement> fLocals;
137 	private CallContext fContext;
138 
139 	private class InlineEvaluator extends HierarchicalASTVisitor {
140 		private ParameterData fFormalArgument;
141 		private boolean fResult;
InlineEvaluator(ParameterData argument)142 		public InlineEvaluator(ParameterData argument) {
143 			fFormalArgument= argument;
144 		}
getResult()145 		public boolean getResult() {
146 			return fResult;
147 		}
setResult(boolean result)148 		private boolean setResult(boolean result) {
149 			fResult= result;
150 			return false;
151 		}
152 		@Override
visit(Expression node)153 		public boolean visit(Expression node) {
154 			int accessMode= fFormalArgument.getSimplifiedAccessMode();
155 			if (accessMode == FlowInfo.WRITE)
156 				return setResult(false);
157 			if (accessMode == FlowInfo.UNUSED)
158 				return setResult(true);
159 			if (ASTNodes.isLiteral(node))
160 				return setResult(true);
161 			return setResult(fFormalArgument.getNumberOfAccesses() <= 1);
162 		}
163 		@Override
visit(SimpleName node)164 		public boolean visit(SimpleName node) {
165 			IBinding binding= node.resolveBinding();
166 			if (binding instanceof IVariableBinding) {
167 				int accessMode = fFormalArgument.getSimplifiedAccessMode();
168 				if (fFormalArgument.isFinal() && !Modifier.isFinal(binding.getModifiers())) {
169 					return setResult(false);
170 				}
171 				if (accessMode == FlowInfo.READ || accessMode == FlowInfo.UNUSED)
172 					return setResult(true);
173 				// from now on we only have write accesses.
174 				IVariableBinding vb= (IVariableBinding)binding;
175 				if (vb.isField())
176 					return setResult(false);
177 				return setResult(fFlowInfo.hasAccessMode(fFlowContext, vb, FlowInfo.UNUSED | FlowInfo.WRITE));
178 			}
179 			return setResult(false);
180 		}
181 		@Override
visit(FieldAccess node)182 		public boolean visit(FieldAccess node) {
183 			return visit(node.getName());
184 		}
185 		@Override
visit(SuperFieldAccess node)186 		public boolean visit(SuperFieldAccess node) {
187 			return visit(node.getName());
188 		}
189 		@Override
visit(ThisExpression node)190 		public boolean visit(ThisExpression node) {
191 			int accessMode= fFormalArgument.getSimplifiedAccessMode();
192 			if (accessMode == FlowInfo.READ || accessMode == FlowInfo.UNUSED)
193 				return setResult(true);
194 			return setResult(false);
195 		}
196 	}
197 
CallInliner(ICompilationUnit unit, CompilationUnit targetAstRoot, SourceProvider provider)198 	public CallInliner(ICompilationUnit unit, CompilationUnit targetAstRoot, SourceProvider provider) throws CoreException {
199 		super();
200 		fCUnit= unit;
201 		fBuffer= RefactoringFileBuffers.acquire(fCUnit);
202 		fSourceProvider= provider;
203 		fImportRewrite= StubUtility.createImportRewrite(targetAstRoot, true);
204 		fLocals= new ArrayList<>(3);
205 		fRewrite= ASTRewrite.create(targetAstRoot.getAST());
206 		fRewrite.setTargetSourceRangeComputer(new NoCommentSourceRangeComputer());
207 		fTypeEnvironment= new TypeEnvironment();
208 	}
209 
dispose()210 	public void dispose() {
211 		try {
212 			RefactoringFileBuffers.release(fCUnit);
213 		} catch (CoreException exception) {
214 			JavaManipulationPlugin.log(exception);
215 		}
216 	}
217 
218 
getImportEdit()219 	public ImportRewrite getImportEdit() {
220 		return fImportRewrite;
221 	}
222 
getTargetNode()223 	public ASTNode getTargetNode() {
224 		return fTargetNode;
225 	}
226 
initialize(BodyDeclaration declaration)227 	public void initialize(BodyDeclaration declaration) {
228 		fBodyDeclaration= declaration;
229 		fRootScope= CodeScopeBuilder.perform(declaration, fSourceProvider.getDeclaration().resolveBinding());
230 		fNumberOfLocals= 0;
231 		switch (declaration.getNodeType()) {
232 			case ASTNode.METHOD_DECLARATION:
233 			case ASTNode.INITIALIZER:
234 				fNumberOfLocals= LocalVariableIndex.perform(declaration);
235 				break;
236 		}
237 	}
238 
initialize(ASTNode invocation, int severity)239 	public RefactoringStatus initialize(ASTNode invocation, int severity) {
240 		RefactoringStatus result= new RefactoringStatus();
241 		fInvocation= invocation;
242 		fLocals= new ArrayList<>(3);
243 
244 		checkMethodDeclaration(result, severity);
245 		if (result.getSeverity() >= severity)
246 			return result;
247 
248 		initializeRewriteState();
249 		initializeTargetNode();
250 		flowAnalysis();
251 
252 		fContext= new CallContext(fInvocation, fInvocationScope, fTargetNode.getNodeType(), fImportRewrite);
253 
254 		try {
255 			computeRealArguments();
256 			computeReceiver();
257 		} catch (BadLocationException exception) {
258 			JavaManipulationPlugin.log(exception);
259 		}
260 		checkInvocationContext(result, severity);
261 
262 		return result;
263 	}
264 
initializeRewriteState()265 	private void initializeRewriteState() {
266 		fFieldInitializer= false;
267 		ASTNode parent= fInvocation.getParent();
268 		do {
269 			if (parent instanceof FieldDeclaration) {
270 				fFieldInitializer= true;
271 				return;
272 			} else if (parent instanceof Block) {
273 				return;
274 			}
275 			parent= parent.getParent();
276 		} while (parent != null);
277 	}
278 
initializeTargetNode()279 	private void initializeTargetNode() {
280 		ASTNode parent= fInvocation.getParent();
281 		int nodeType= parent.getNodeType();
282 		if (nodeType == ASTNode.EXPRESSION_STATEMENT || nodeType == ASTNode.RETURN_STATEMENT) {
283 			fTargetNode= parent;
284 		} else {
285 			fTargetNode= fInvocation;
286 		}
287 	}
288 
289 	// the checks depend on invocation context and therefore can't be done in SourceAnalyzer
checkMethodDeclaration(RefactoringStatus result, int severity)290 	private void checkMethodDeclaration(RefactoringStatus result, int severity) {
291 		MethodDeclaration methodDeclaration= fSourceProvider.getDeclaration();
292 		// it is not allowed to inline constructor invocation only if it is used for class instance creation
293 		// if constructor is invoked from another constructor then we can inline such invocation
294 		if (fInvocation.getNodeType() != ASTNode.CONSTRUCTOR_INVOCATION && methodDeclaration.isConstructor()) {
295 			result.addEntry(new RefactoringStatusEntry(
296 				severity,
297 				RefactoringCoreMessages.CallInliner_constructors,
298 				JavaStatusContext.create(fCUnit, fInvocation)));
299 		}
300 		if (fSourceProvider.hasSuperMethodInvocation() && fInvocation.getNodeType() == ASTNode.METHOD_INVOCATION) {
301 			Expression receiver= ((MethodInvocation)fInvocation).getExpression();
302 			if (receiver instanceof ThisExpression) {
303 				result.addEntry(new RefactoringStatusEntry(
304 					severity,
305 					RefactoringCoreMessages.CallInliner_super_into_this_expression,
306 					JavaStatusContext.create(fCUnit, fInvocation)));
307 			}
308 		}
309 	}
310 
checkInvocationContext(RefactoringStatus result, int severity)311 	private void checkInvocationContext(RefactoringStatus result, int severity) {
312 		if (fInvocation.getNodeType() == ASTNode.METHOD_INVOCATION) {
313 			if (((MethodInvocation)fInvocation).resolveTypeBinding() == null) {
314 				addEntry(result, RefactoringCoreMessages.CallInliner_receiver_type,
315 					RefactoringStatusCodes.INLINE_METHOD_NULL_BINDING, severity);
316 				return;
317 			}
318 		}
319 		int nodeType= fTargetNode.getNodeType();
320 		if (nodeType == ASTNode.EXPRESSION_STATEMENT) {
321 			if (fSourceProvider.isExecutionFlowInterrupted()) {
322 				addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow,
323 					RefactoringStatusCodes.INLINE_METHOD_EXECUTION_FLOW, severity);
324 				return;
325 			}
326 		} else if (nodeType == ASTNode.METHOD_INVOCATION) {
327 			ASTNode parent= fTargetNode.getParent();
328 			if (isReturnStatement(parent)) {
329 				//support inlining even if the execution flow is interrupted
330 				return;
331 			}
332 			if (fSourceProvider.isExecutionFlowInterrupted()) {
333 				addEntry(result, RefactoringCoreMessages.CallInliner_execution_flow,
334 					RefactoringStatusCodes.INLINE_METHOD_EXECUTION_FLOW, severity);
335 				return;
336 			}
337 			if (isAssignment(parent) || isSingleDeclaration(parent)) {
338 				// we support inlining expression in assigment and initializers as
339 				// long as the execution flow isn't interrupted.
340 				return;
341 			} else {
342 				boolean isFieldDeclaration= ASTNodes.getParent(fInvocation, FieldDeclaration.class) != null;
343 				if (!fSourceProvider.isSimpleFunction()) {
344 					if (isMultiDeclarationFragment(parent)) {
345 						addEntry(result, RefactoringCoreMessages.CallInliner_multiDeclaration,
346 							RefactoringStatusCodes.INLINE_METHOD_INITIALIZER_IN_FRAGEMENT, severity);
347 					} else if (isFieldDeclaration) {
348 						addEntry(result,
349 							RefactoringCoreMessages.CallInliner_field_initializer_simple,
350 							RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity);
351 					} else {
352 						addEntry(result, RefactoringCoreMessages.CallInliner_simple_functions,
353 							RefactoringStatusCodes.INLINE_METHOD_ONLY_SIMPLE_FUNCTIONS, severity);
354 					}
355 					return;
356 				}
357 				if (isFieldDeclaration) {
358 					int argumentsCount= fContext.arguments.length;
359 					for (int i= 0; i < argumentsCount; i++) {
360 						ParameterData parameter= fSourceProvider.getParameterData(i);
361 						if(parameter.isWrite()) {
362 							addEntry(result,
363 								RefactoringCoreMessages.CallInliner_field_initialize_write_parameter,
364 								RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity);
365 							return;
366 						}
367 					}
368 					if(fLocals.size() > 0) {
369 						addEntry(result,
370 							RefactoringCoreMessages.CallInliner_field_initialize_new_local,
371 							RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity);
372 						return;
373 					}
374 					// verify that the field is not referenced by the initializer method
375 					VariableDeclarationFragment variable= (VariableDeclarationFragment)ASTNodes.getParent(fInvocation, ASTNode.VARIABLE_DECLARATION_FRAGMENT);
376 					if(fSourceProvider.isVariableReferenced(variable.resolveBinding())) {
377 						addEntry(result,
378 							RefactoringCoreMessages.CallInliner_field_initialize_self_reference,
379 							RefactoringStatusCodes.INLINE_METHOD_FIELD_INITIALIZER, severity);
380 						return;
381 					}
382 				}
383 			}
384 		}
385 	}
386 
isAssignment(ASTNode node)387 	private static boolean isAssignment(ASTNode node) {
388 		return node instanceof Assignment;
389 	}
390 
isReturnStatement(ASTNode node)391 	private static boolean isReturnStatement(ASTNode node) {
392 		return node instanceof ReturnStatement;
393 	}
394 
isSingleDeclaration(ASTNode node)395 	private static boolean isSingleDeclaration(ASTNode node) {
396 		int type= node.getNodeType();
397 		if (type == ASTNode.SINGLE_VARIABLE_DECLARATION)
398 			return true;
399 		if (type == ASTNode.VARIABLE_DECLARATION_FRAGMENT) {
400 			node= node.getParent();
401 			if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) {
402 				VariableDeclarationStatement vs= (VariableDeclarationStatement)node;
403 				return vs.fragments().size() == 1;
404 			}
405 		}
406 		return false;
407 	}
408 
isMultiDeclarationFragment(ASTNode node)409 	private static boolean isMultiDeclarationFragment(ASTNode node) {
410 		int nodeType= node.getNodeType();
411 		if (nodeType == ASTNode.VARIABLE_DECLARATION_FRAGMENT) {
412 			node= node.getParent();
413 			if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) {
414 				VariableDeclarationStatement vs= (VariableDeclarationStatement)node;
415 				return vs.fragments().size() > 1;
416 			}
417 		}
418 		return false;
419 	}
420 
addEntry(RefactoringStatus result, String message, int code, int severity)421 	private void addEntry(RefactoringStatus result, String message, int code, int severity) {
422 		result.addEntry(new RefactoringStatusEntry(
423 			severity, message,
424 			JavaStatusContext.create(fCUnit, fInvocation),
425 			CorextCore.getPluginId(),
426 			code, null));
427 	}
428 
flowAnalysis()429 	private void flowAnalysis() {
430 		fInvocationScope= fRootScope.findScope(fTargetNode.getStartPosition(), fTargetNode.getLength());
431 		fInvocationScope.setCursor(fTargetNode.getStartPosition());
432 		fFlowContext= new FlowContext(0, fNumberOfLocals + 1);
433 		fFlowContext.setConsiderAccessMode(true);
434 		fFlowContext.setComputeMode(FlowContext.ARGUMENTS);
435 		Selection selection= Selection.createFromStartLength(fInvocation.getStartPosition(), fInvocation.getLength());
436 		switch (fBodyDeclaration.getNodeType()) {
437 			case ASTNode.INITIALIZER:
438 			case ASTNode.FIELD_DECLARATION:
439 			case ASTNode.METHOD_DECLARATION:
440 			case ASTNode.ENUM_CONSTANT_DECLARATION:
441 				fFlowInfo= new InputFlowAnalyzer(fFlowContext, selection, true).perform(fBodyDeclaration);
442 				break;
443 			default:
444 				Assert.isTrue(false, "Should not happen");			 //$NON-NLS-1$
445 		}
446 	}
447 
perform(TextEditGroup textEditGroup)448 	public RefactoringStatus perform(TextEditGroup textEditGroup) throws CoreException {
449 		RefactoringStatus result= new RefactoringStatus();
450 		String[] blocks= fSourceProvider.getCodeBlocks(fContext, fImportRewrite);
451 		if(!fFieldInitializer) {
452 			initializeInsertionPoint(fSourceProvider.getNumberOfStatements() + fLocals.size());
453 		}
454 
455 		addNewLocals(textEditGroup);
456 		replaceCall(result, blocks, textEditGroup);
457 		return result;
458 	}
459 
getModifications()460 	public TextEdit getModifications() {
461 		return fRewrite.rewriteAST(fBuffer.getDocument(), fCUnit.getJavaProject().getOptions(true));
462 	}
463 
computeRealArguments()464 	private void computeRealArguments() {
465 		List<Expression> arguments= Invocations.getArguments(fInvocation);
466 		Set<Expression> canNotInline= crossCheckArguments(arguments);
467 		boolean needsVarargBoxing= needsVarargBoxing(arguments);
468 		int varargIndex= fSourceProvider.getVarargIndex();
469 		AST ast= fInvocation.getAST();
470 		Expression[] realArguments= new Expression[needsVarargBoxing ? varargIndex + 1 : arguments.size()];
471 		for (int i= 0; i < (needsVarargBoxing ? varargIndex : arguments.size()); i++) {
472 			Expression expression= arguments.get(i);
473 			ParameterData parameter= fSourceProvider.getParameterData(i);
474 			if (canInline(expression, parameter) && !canNotInline.contains(expression)) {
475 				realArguments[i]= expression;
476 			} else {
477 				String name= fInvocationScope.createName(parameter.getName(), true);
478 				realArguments[i]= ast.newSimpleName(name);
479 				VariableDeclarationStatement local= createLocalDeclaration(parameter.getTypeBinding(), name, (Expression) fRewrite.createCopyTarget(expression));
480 				if (parameter.isFinal()) {
481 					local.modifiers().add(fInvocation.getAST().newModifier(ModifierKeyword.FINAL_KEYWORD));
482 				}
483 				fLocals.add(local);
484 			}
485 		}
486 		if (needsVarargBoxing) {
487 			ParameterData parameter= fSourceProvider.getParameterData(varargIndex);
488 			String name= fInvocationScope.createName(parameter.getName(), true);
489 			realArguments[varargIndex]= ast.newSimpleName(name);
490 			Type type= fImportRewrite.addImport(parameter.getTypeBinding(), ast);
491 			VariableDeclarationFragment fragment= ast.newVariableDeclarationFragment();
492 			fragment.setName(ast.newSimpleName(name));
493 			ArrayInitializer initializer= ast.newArrayInitializer();
494 			for (int i= varargIndex; i < arguments.size(); i++) {
495 				initializer.expressions().add(fRewrite.createCopyTarget(arguments.get(i)));
496 			}
497 			fragment.setInitializer(initializer);
498 			VariableDeclarationStatement decl= ast.newVariableDeclarationStatement(fragment);
499 			decl.setType(type);
500 			fLocals.add(decl);
501 		}
502 		fContext.compilationUnit= fCUnit;
503 		fContext.arguments= realArguments;
504 	}
505 
needsVarargBoxing(List<Expression> arguments)506 	private boolean needsVarargBoxing(List<Expression> arguments) {
507 		if (!fSourceProvider.isVarargs())
508 			return false;
509 		/*
510 		if (!fSourceProvider.hasArrayAccess())
511 			return false;
512 		*/
513 		int index= fSourceProvider.getVarargIndex();
514 		// we have varags but the call doesn't pass any arguments
515 		if (index >= arguments.size())
516 			return true;
517 		// parameter is array type
518 		// one arg
519 		if (index == arguments.size() - 1) {
520 			ITypeBinding argument= arguments.get(index).resolveTypeBinding();
521 			if (argument == null)
522 				return false;
523 			ITypeBinding parameter= fSourceProvider.getParameterData(index).getTypeBinding();
524 			return !fTypeEnvironment.create(argument).canAssignTo(fTypeEnvironment.create(parameter));
525 		}
526 		return true;
527 	}
528 
computeReceiver()529 	private void computeReceiver() throws BadLocationException {
530 		Expression receiver= Invocations.getExpression(fInvocation);
531 		if (receiver == null)
532 			return;
533 		final boolean isName= receiver instanceof Name;
534 		if (isName)
535 			fContext.receiverIsStatic= ((Name)receiver).resolveBinding() instanceof ITypeBinding;
536 		if (ASTNodes.isLiteral(receiver) || isName || receiver instanceof ThisExpression) {
537 			fContext.receiver= fBuffer.getDocument().get(receiver.getStartPosition(), receiver.getLength());
538 			return;
539 		}
540 		switch(fSourceProvider.getReceiversToBeUpdated()) {
541 			case 0:
542 				// Make sure we evaluate the current receiver. Best is to assign to
543 				// local.
544 				fLocals.add(createLocalDeclaration(
545 					receiver.resolveTypeBinding(),
546 					fInvocationScope.createName("r", true),  //$NON-NLS-1$
547 					(Expression)fRewrite.createCopyTarget(receiver)));
548 				return;
549 			case 1:
550 				fContext.receiver= fBuffer.getDocument().get(receiver.getStartPosition(), receiver.getLength());
551 				return;
552 			default:
553 				String local= fInvocationScope.createName("r", true); //$NON-NLS-1$
554 					fLocals.add(createLocalDeclaration(
555 					receiver.resolveTypeBinding(),
556 					local,
557 					(Expression)fRewrite.createCopyTarget(receiver)));
558 				fContext.receiver= local;
559 				return;
560 		}
561 	}
562 
addNewLocals(TextEditGroup textEditGroup)563 	private void addNewLocals(TextEditGroup textEditGroup) {
564 		if (fLocals.isEmpty())
565 			return;
566 		for (VariableDeclarationStatement variableDeclarationStatement : fLocals) {
567 			ASTNode element= variableDeclarationStatement;
568 			fListRewrite.insertAt(element, fInsertionIndex++, textEditGroup);
569 		}
570 	}
571 
replaceCall(RefactoringStatus status, String[] blocks, TextEditGroup textEditGroup)572 	private void replaceCall(RefactoringStatus status, String[] blocks, TextEditGroup textEditGroup) {
573 		// Inline empty body
574 		if (blocks.length == 0 && fTargetNode != null) {
575 			if (fNeedsStatement) {
576 				fRewrite.replace(fTargetNode, fTargetNode.getAST().newEmptyStatement(), textEditGroup);
577 			} else {
578 				fRewrite.remove(fTargetNode, textEditGroup);
579 			}
580 		} else {
581 			ASTNode node= null;
582 			for (int i= 0; i < blocks.length - 1; i++) {
583 				node= fRewrite.createStringPlaceholder(blocks[i], ASTNode.RETURN_STATEMENT);
584 				fListRewrite.insertAt(node, fInsertionIndex++, textEditGroup);
585 			}
586 			String block= blocks[blocks.length - 1];
587 			// We can inline a call where the declaration is a function and the call itself
588 			// is a statement. In this case we have to create a temporary variable if the
589 			// returned expression must be evaluated.
590 			if (fContext.callMode == ASTNode.EXPRESSION_STATEMENT && fSourceProvider.hasReturnValue()) {
591 				if (fSourceProvider.mustEvaluateReturnedExpression()) {
592 					if (fSourceProvider.returnValueNeedsLocalVariable()) {
593 						IMethodBinding invocation= Invocations.resolveBinding(fInvocation);
594 						node= createLocalDeclaration(
595 							invocation.getReturnType(),
596 							fInvocationScope.createName(fSourceProvider.getMethodName(), true),
597 							(Expression)fRewrite.createStringPlaceholder(block, ASTNode.METHOD_INVOCATION));
598 					} else {
599 						node= fRewrite.getAST().newExpressionStatement(
600 							(Expression)fRewrite.createStringPlaceholder(block, ASTNode.METHOD_INVOCATION));
601 					}
602 				} else {
603 					node= null;
604 				}
605 			} else if (fTargetNode instanceof Expression) {
606 				node= fRewrite.createStringPlaceholder(block, ASTNode.METHOD_INVOCATION);
607 
608 				// fixes bug #24941
609 				if (needsExplicitCast(status)) {
610 					AST ast= node.getAST();
611 					CastExpression castExpression= ast.newCastExpression();
612 					Type returnType= fImportRewrite.addImport(fSourceProvider.getReturnType(), ast);
613 					castExpression.setType(returnType);
614 
615 					if (NecessaryParenthesesChecker.needsParentheses(fSourceProvider.getReturnExpressions().get(0), castExpression, CastExpression.EXPRESSION_PROPERTY)) {
616 						ParenthesizedExpression parenthesized= ast.newParenthesizedExpression();
617 						parenthesized.setExpression((Expression)node);
618 						node= parenthesized;
619 					}
620 
621 					castExpression.setExpression((Expression)node);
622 					node= castExpression;
623 
624 					if (NecessaryParenthesesChecker.needsParentheses(castExpression, fTargetNode.getParent(), fTargetNode.getLocationInParent())) {
625 						ParenthesizedExpression parenthesized= ast.newParenthesizedExpression();
626 						parenthesized.setExpression((Expression)node);
627 						node= parenthesized;
628 					}
629 				} else if (fSourceProvider.needsReturnedExpressionParenthesis(fTargetNode.getParent(), fTargetNode.getLocationInParent())) {
630 					ParenthesizedExpression pExp= fTargetNode.getAST().newParenthesizedExpression();
631 					pExp.setExpression((Expression)node);
632 					node= pExp;
633 				}
634 			} else {
635 				node= fRewrite.createStringPlaceholder(block, ASTNode.RETURN_STATEMENT);
636 			}
637 
638 			// Now replace the target node with the source node
639 			if (node != null) {
640 				if (fTargetNode == null) {
641 					fListRewrite.insertAt(node, fInsertionIndex++, textEditGroup);
642 				} else {
643 					fRewrite.replace(fTargetNode, node, textEditGroup);
644 				}
645 			} else {
646 				if (fTargetNode != null) {
647 					fRewrite.remove(fTargetNode, textEditGroup);
648 				}
649 			}
650 		}
651 	}
652 
653 	/**
654 	 * @param status the status
655 	 * @return <code>true</code> if explicit cast is needed otherwise <code>false</code>
656 	 */
needsExplicitCast(RefactoringStatus status)657 	private boolean needsExplicitCast(RefactoringStatus status) {
658 		// if the return type of the method is the same as the type of the
659 		// returned expression then we don't need an explicit cast.
660 		if (fSourceProvider.returnTypeMatchesReturnExpressions())
661 				return false;
662 
663 		List<Expression> returnExprs= fSourceProvider.getReturnExpressions();
664 		// it is inferred that only methods consisting of a single
665 		// return statement can be inlined as parameters in other
666 		// method invocations
667 		if (returnExprs.size() != 1)
668 			return false;
669 
670 		if (fTargetNode.getLocationInParent() == MethodInvocation.ARGUMENTS_PROPERTY) {
671 			MethodInvocation methodInvocation= (MethodInvocation)fTargetNode.getParent();
672 			if(methodInvocation.getExpression() == fTargetNode)
673 				return false;
674 			IMethodBinding method= methodInvocation.resolveMethodBinding();
675 			if (method == null) {
676 				status.addError(RefactoringCoreMessages.CallInliner_cast_analysis_error,
677 					JavaStatusContext.create(fCUnit, methodInvocation));
678 				return false;
679 			}
680 
681 			ITypeBinding parameterType= returnExprs.get(0).resolveTypeBinding();
682 			return ASTNodes.isTargetAmbiguous((Expression) fTargetNode, parameterType);
683 		} else {
684 			ITypeBinding explicitCast= ASTNodes.getExplicitCast(returnExprs.get(0), (Expression)fTargetNode);
685 			return explicitCast != null;
686 		}
687 	}
688 
createLocalDeclaration(ITypeBinding type, String name, Expression initializer)689 	private VariableDeclarationStatement createLocalDeclaration(ITypeBinding type, String name, Expression initializer) {
690 		ImportRewriteContext context= new ContextSensitiveImportRewriteContext(fTargetNode, fImportRewrite);
691 		String typeName= fImportRewrite.addImport(type, context);
692 		VariableDeclarationStatement decl= (VariableDeclarationStatement)ASTNodeFactory.newStatement(
693 			fInvocation.getAST(), typeName + " " + name + ";"); //$NON-NLS-1$ //$NON-NLS-2$
694 		((VariableDeclarationFragment)decl.fragments().get(0)).setInitializer(initializer);
695 		return decl;
696 	}
697 
698     /**
699      * Checks whether arguments are passed to the method which do some assignments
700      * inside the expression. If so these arguments can't be inlined into the
701      * calling method since the assignments might be reorder. An example is:
702      * <code>
703      *   add((field=args).length,field.hashCode());
704      * </code>
705      * Field might not be initialized when the arguments are reorder in the called
706      * method.
707      * @param arguments the arguments
708      * @return all arguments that cannot be inlined
709      */
crossCheckArguments(List<Expression> arguments)710 	private Set<Expression> crossCheckArguments(List<Expression> arguments) {
711 		final Set<IBinding> assigned= new HashSet<>();
712 		final Set<Expression> result= new HashSet<>();
713 		for (Expression expression : arguments) {
714 			expression.accept(new ASTVisitor() {
715 				@Override
716 				public boolean visit(Assignment node) {
717 					Expression lhs= node.getLeftHandSide();
718 					if (lhs instanceof Name) {
719 						IBinding binding= ((Name)lhs).resolveBinding();
720 						if (binding instanceof IVariableBinding) {
721 							assigned.add(binding);
722 							result.add(expression);
723 						}
724 					}
725 					return true;
726 				}
727 			});
728 		}
729 		for (Expression expression : arguments) {
730 			if (!result.contains(expression)) {
731 				expression.accept(new HierarchicalASTVisitor() {
732 					@Override
733 					public boolean visit(Name node) {
734 						IBinding binding= node.resolveBinding();
735 						if (binding != null && assigned.contains(binding))
736 							result.add(expression);
737 						return false;
738 					}
739 				});
740 			}
741 		}
742 		return result;
743 	}
744 
canInline(Expression actualParameter, ParameterData formalParameter)745 	private boolean canInline(Expression actualParameter, ParameterData formalParameter) {
746 		InlineEvaluator evaluator= new InlineEvaluator(formalParameter);
747 		actualParameter.accept(evaluator);
748 		return evaluator.getResult();
749 	}
750 
initializeInsertionPoint(int nos)751 	private void initializeInsertionPoint(int nos) {
752 		fInsertionIndex= -1;
753 		fNeedsStatement= false;
754 		// if we have a constructor invocation the invocation itself is already a statement
755 		ASTNode parentStatement= fInvocation instanceof Statement
756 			? fInvocation
757 			: ASTNodes.getParent(fInvocation, Statement.class);
758 		if (parentStatement == null)
759 			return;
760 
761 		ASTNode container= parentStatement.getParent();
762 		int type= container.getNodeType();
763 		if (type == ASTNode.BLOCK) {
764 			Block block= (Block)container;
765 			fListRewrite= fRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY);
766 			fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement);
767 		} else if (type == ASTNode.SWITCH_STATEMENT) {
768 			SwitchStatement switchStatement= (SwitchStatement)container;
769 			fListRewrite= fRewrite.getListRewrite(switchStatement, SwitchStatement.STATEMENTS_PROPERTY);
770 			fInsertionIndex= fListRewrite.getRewrittenList().indexOf(parentStatement);
771 		} else if (isControlStatement(container) || type == ASTNode.LABELED_STATEMENT) {
772 			fNeedsStatement= true;
773 			if (nos > 1 || needsBlockAroundDanglingIf()) {
774 				Block block= fInvocation.getAST().newBlock();
775 				fInsertionIndex= 0;
776 				Statement currentStatement= null;
777 				switch(type) {
778 					case ASTNode.LABELED_STATEMENT:
779 						currentStatement= ((LabeledStatement)container).getBody();
780 						break;
781 					case ASTNode.FOR_STATEMENT:
782 						currentStatement= ((ForStatement)container).getBody();
783 						break;
784 					case ASTNode.ENHANCED_FOR_STATEMENT:
785 						currentStatement= ((EnhancedForStatement)container).getBody();
786 						break;
787 					case ASTNode.WHILE_STATEMENT:
788 						currentStatement= ((WhileStatement)container).getBody();
789 						break;
790 					case ASTNode.DO_STATEMENT:
791 						currentStatement= ((DoStatement)container).getBody();
792 						break;
793 					case ASTNode.IF_STATEMENT:
794 						IfStatement node= (IfStatement)container;
795 						Statement thenPart= node.getThenStatement();
796 						if (fTargetNode == thenPart || ASTNodes.isParent(fTargetNode, thenPart)) {
797 							currentStatement= thenPart;
798 						} else {
799 							currentStatement= node.getElseStatement();
800 						}
801 						break;
802 				}
803 				Assert.isNotNull(currentStatement);
804 				fRewrite.replace(currentStatement, block, null);
805 				fListRewrite= fRewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY);
806 				// The method to be inlined is not the body of the control statement.
807 				if (currentStatement != fTargetNode) {
808 					fListRewrite.insertLast(fRewrite.createCopyTarget(currentStatement), null);
809 				} else {
810 					// We can't replace a copy with something else. So we
811 					// have to insert all statements to be inlined.
812 					fTargetNode= null;
813 				}
814 			}
815 		}
816 		// We only insert one new statement or we delete the existing call.
817 		// So there is no need to have an insertion index.
818 	}
819 
needsBlockAroundDanglingIf()820 	private boolean needsBlockAroundDanglingIf() {
821 		/* see https://bugs.eclipse.org/bugs/show_bug.cgi?id=169331
822 		 *
823 		 * Situation:
824 		 * boolean a, b;
825 		 * void toInline() {
826 		 *     if (a)
827 		 *         hashCode();
828 		 * }
829 		 * void m() {
830 		 *     if (b)
831 		 *         toInline();
832 		 *     else
833 		 *         toString();
834 		 * }
835 		 * => needs block around inlined "if (a)..." to avoid attaching else to wrong if.
836 		 */
837 		return fTargetNode.getLocationInParent() == IfStatement.THEN_STATEMENT_PROPERTY
838 				&& fTargetNode.getParent().getStructuralProperty(IfStatement.ELSE_STATEMENT_PROPERTY) != null
839 				&& fSourceProvider.isDangligIf();
840 	}
841 
isControlStatement(ASTNode node)842 	private boolean isControlStatement(ASTNode node) {
843 		int type= node.getNodeType();
844 		return type == ASTNode.IF_STATEMENT || type == ASTNode.FOR_STATEMENT || type == ASTNode.ENHANCED_FOR_STATEMENT ||
845 		        type == ASTNode.WHILE_STATEMENT || type == ASTNode.DO_STATEMENT;
846 	}
847 }
848