Newer
Older
package tfm.graphs.sdg;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.body.VariableDeclarator;
import com.github.javaparser.ast.expr.Expression;
import com.github.javaparser.ast.expr.VariableDeclarationExpr;
import com.github.javaparser.ast.type.Type;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import com.github.javaparser.resolution.UnsolvedSymbolException;
import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration;
import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.SourceFileInfoExtractor;
import com.github.javaparser.symbolsolver.javaparsermodel.JavaParserFacade;
import com.github.javaparser.symbolsolver.model.resolution.SymbolReference;
import com.github.javaparser.symbolsolver.model.resolution.TypeSolver;
import com.github.javaparser.symbolsolver.resolution.SymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import tfm.nodes.TypeNodeFactory;
import tfm.nodes.type.NodeType;
import tfm.utils.Context;
import tfm.utils.Logger;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
class MethodCallReplacerVisitor extends VoidVisitorAdapter<Context> {
public MethodCallReplacerVisitor(SDG sdg) {
this.sdg = sdg;
}
private void searchAndSetMethodCallNode(Node node) {
Optional<GraphNode<Node>> optionalNode = sdg.findNodeByASTNode(node);
assert optionalNode.isPresent();
methodCallNode = optionalNode.get();
}
@Override
searchAndSetMethodCallNode(n);
super.visit(n, arg);
}
public void visit(ForEachStmt n, Context arg) {
searchAndSetMethodCallNode(n);
super.visit(n, arg);
}
@Override
public void visit(ForStmt n, Context arg) {
searchAndSetMethodCallNode(n);
super.visit(n, arg);
}
@Override
public void visit(IfStmt n, Context arg) {
searchAndSetMethodCallNode(n);
super.visit(n, arg);
}
@Override
public void visit(SwitchStmt n, Context arg) {
searchAndSetMethodCallNode(n);
super.visit(n, arg);
}
@Override
public void visit(WhileStmt n, Context arg) {
searchAndSetMethodCallNode(n);
super.visit(n, arg);
}
@Override
public void visit(ReturnStmt n, Context arg) {
searchAndSetMethodCallNode(n);
super.visit(n, arg);
}
@Override
public void visit(ExpressionStmt n, Context arg) {
searchAndSetMethodCallNode(n);
}
@Override
public void visit(MethodCallExpr methodCallExpr, Context context) {
List<Expression> arguments = methodCallExpr.getArguments();
// // Parse first method call expressions as arguments
// arguments.stream()
// .filter(Expression::isMethodCallExpr)
// .forEach(expression -> expression.accept(this, context));
Logger.log("MethodCallReplacerVisitor", context);
Optional<GraphNode<MethodDeclaration>> optionalNethodDeclarationNode = getMethodDeclarationNodeWithJavaParser(methodCallExpr);
if (!optionalNethodDeclarationNode.isPresent()) {
Logger.format("Not found: '%s'. Discarding");
GraphNode<MethodDeclaration> calledMethodNode = optionalNethodDeclarationNode.get();
MethodDeclaration methodCalled = calledMethodNode.getAstNode();
sdg.addCallArc(methodCallNode, calledMethodNode);
NodeList<Parameter> parameters = calledMethodNode.getAstNode().getParameters();
for (int i = 0; i < parameters.size(); i++) {
Parameter parameter = parameters.get(i);
Expression argument = arguments.get(i);
// In expression
VariableDeclarationExpr inVariableDeclarationExpr = new VariableDeclarationExpr(
new VariableDeclarator(
parameter.getType(),
parameter.getNameAsString() + "_in",
)
);
ExpressionStmt inExprStmt = new ExpressionStmt(inVariableDeclarationExpr);
GraphNode<ExpressionStmt> argumentInNode = sdg.addNode(inExprStmt.toString(), inExprStmt, TypeNodeFactory.fromType(NodeType.VARIABLE_IN));
// Out expression
VariableDeclarationExpr outVariableDeclarationExpr = new VariableDeclarationExpr(
new VariableDeclarator(
parameter.getType(),
parameter.getNameAsString(),
new NameExpr(parameter.getNameAsString() + "_out")
)
);
ExpressionStmt outExprStmt = new ExpressionStmt(outVariableDeclarationExpr);
GraphNode<ExpressionStmt> argumentOutNode = sdg.addNode(outExprStmt.toString(), outExprStmt, TypeNodeFactory.fromType(NodeType.VARIABLE_OUT));
Logger.log("MethodCallReplacerVisitor", String.format("%s | Method '%s' called", methodCallExpr, methodCalled.getNameAsString()));
private Optional<GraphNode<MethodDeclaration>> getMethodDeclarationNodeWithJavaParser(MethodCallExpr methodCallExpr) {
TypeSolver typeSolver = new ReflectionTypeSolver();
try {
SymbolReference<ResolvedMethodDeclaration> solver = JavaParserFacade.get(typeSolver).solve(methodCallExpr);
return solver.isSolved()
? solver.getCorrespondingDeclaration().toAst()
.flatMap(methodDeclaration -> sdg.findNodeByASTNode(methodDeclaration))
: Optional.empty();
} catch (UnsolvedSymbolException e) {
return Optional.empty();
}
}
/**
* Handles method calls with scope. Examples:
* - System.out.println() -> println() is a method call with scope System.out
* - new A().getB() -> getB() is a method call with scope new A()
*/
private Optional<MethodDeclaration> shouldMakeCallWithScope(MethodCallExpr methodCallExpr, Context context) {
assert methodCallExpr.getScope().isPresent();
String scopeName = methodCallExpr.getScope().get().toString();
if (!context.getCurrentClass().isPresent()) {
return Optional.empty();
}
ClassOrInterfaceDeclaration currentClass = context.getCurrentClass().get();
// Check if it's a static method call of current class
if (!Objects.equals(scopeName, currentClass.getNameAsString())) {
// Check if 'scopeName' is a variable
List<GraphNode<?>> declarations = sdg.findDeclarationsOfVariable(scopeName, methodCallNode);
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
if (declarations.isEmpty()) {
// It is a static method call of another class. We do nothing
return Optional.empty();
}
/*
It's a variable since it has declarations. We now have to check if the class name
is the same as the current class (the object is an instance of our class)
*/
GraphNode<?> declarationNode = declarations.get(declarations.size() - 1);
ExpressionStmt declarationExpr = (ExpressionStmt) declarationNode.getAstNode();
VariableDeclarationExpr variableDeclarationExpr = declarationExpr.getExpression().asVariableDeclarationExpr();
Optional<VariableDeclarator> optionalVariableDeclarator = variableDeclarationExpr.getVariables().stream()
.filter(variableDeclarator -> Objects.equals(variableDeclarator.getNameAsString(), scopeName))
.findFirst();
if (!optionalVariableDeclarator.isPresent()) {
// should not happen
return Optional.empty();
}
Type variableType = optionalVariableDeclarator.get().getType();
if (!variableType.isClassOrInterfaceType()) {
// Not class type
return Optional.empty();
}
if (!Objects.equals(variableType.asClassOrInterfaceType().getNameAsString(), currentClass.getNameAsString())) {
// object is not instance of our class
return Optional.empty();
}
// if we got here, the object is instance of our class
}
// It's a static method call to a method of the current class
return findMethodInClass(methodCallExpr, currentClass);
}
private Optional<MethodDeclaration> shouldMakeCallWithNoScope(MethodCallExpr methodCallExpr, Context context) {
assert !methodCallExpr.getScope().isPresent();
/*
May be a call to a method of the current class or a call to an imported static method.
In the first case, we make the call. Otherwise, not.
*/
if (!context.getCurrentClass().isPresent()) {
return Optional.empty();
}
// We get the current class and search along their methods to find the one we're looking for...
ClassOrInterfaceDeclaration currentClass = context.getCurrentClass().get();
return findMethodInClass(methodCallExpr, currentClass);
}
private Optional<MethodDeclaration> findMethodInClass(MethodCallExpr methodCallExpr, ClassOrInterfaceDeclaration klass) {
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
int argumentsCount = methodCallExpr.getArguments().size();
// Get methods with equal name and parameter count
List<MethodDeclaration> classMethods = klass.getMethodsByName(methodCallExpr.getNameAsString()).stream()
// Filter methods with equal or less (varargs) number of parameters
.filter(methodDeclaration -> {
NodeList<Parameter> parameters = methodDeclaration.getParameters();
if (parameters.size() == argumentsCount) {
return true;
}
if (parameters.isEmpty()) {
return false;
}
// There are more arguments than parameters. May be OK if last parameter is varargs
if (parameters.size() < argumentsCount) {
return parameters.get(parameters.size() - 1).isVarArgs();
}
// There are less arguments than parameters. May be OK if the last parameter is varargs
// and it's omited
return parameters.size() - 1 == argumentsCount
&& parameters.get(parameters.size() - 1).isVarArgs();
})
.collect(Collectors.toList());
// No methods in class with that name and parameter count
return Optional.empty();
}
if (classMethods.size() == 1) {
// We found the method!
return Optional.of(classMethods.get(0));
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/*
* Tricky one! We have to match argument and parameter types, so we have to:
* - Differentiate arguments expressions:
* - Easy: In case of CastExpr, get the type
* - Easy: In case of ObjectCreationExpr, get the type
* - Medium: In case of NameExpr, find the declaration and its type
* - Medium: In case of LiteralExpr, get the type
* - Hard: In case of MethodCallExpr, find MethodDeclaration and its type
* - If there is a varargs parameter, check every argument corresponding to it has the same type
*
* Example:
* At this point these three methods are considered as called:
* private void foo(int a, int b) {}
* private void foo(String a, String b) {}
* private void foo(String a, int... bs) {}
*
* We have to match types to get the correct one
*
* */
return classMethods.stream().filter(methodDeclaration -> {
boolean match = true;
for (int i = 0; i < methodDeclaration.getParameters().size(); i++) {
if (!match) {
break;
}
if (argumentsCount < i) {
return argumentsCount == i - 1
&& methodDeclaration.getParameter(i).isVarArgs();
}
// TODO - Convert into a visitor
Expression argumentExpression = methodCallExpr.getArgument(i);
Parameter parameter = methodDeclaration.getParameter(i);
if (argumentExpression.isCastExpr()) {
match = Objects.equals(argumentExpression.asCastExpr().getType(), parameter.getType());
} else if (argumentExpression.isObjectCreationExpr()) {
match = Objects.equals(argumentExpression.asObjectCreationExpr().getType(), parameter.getType());
} else if (argumentExpression.isNameExpr()) {
String variableName = argumentExpression.asNameExpr().getNameAsString();
List<GraphNode<?>> declarationsOfVariable = sdg.findDeclarationsOfVariable(argumentExpression.asNameExpr().getNameAsString(), methodCallNode);
assert !declarationsOfVariable.isEmpty();
GraphNode<?> declarationNode = declarationsOfVariable.get(declarationsOfVariable.size() - 1);
ExpressionStmt expressionStmt = (ExpressionStmt) declarationNode.getAstNode();
assert expressionStmt.getExpression().isVariableDeclarationExpr();
match = expressionStmt.getExpression().asVariableDeclarationExpr().getVariables().stream()
.filter(variableDeclarator -> Objects.equals(variableDeclarator.getName().asString(), variableName))
.findFirst()
.map(variableDeclarator -> Objects.equals(variableDeclarator.getType(), parameter.getType()))
.orElse(false);
} // TODO: More checks
}
return match;
}).findFirst();