/** * Wire * Copyright (C) 2016 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.waz.lintrules.issues; import com.android.annotations.NonNull; import com.android.tools.lint.client.api.JavaParser; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Implementation; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.JavaContext; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; import lombok.ast.AstVisitor; import lombok.ast.BinaryExpression; import lombok.ast.BinaryOperator; import lombok.ast.Expression; import lombok.ast.FloatingPointLiteral; import lombok.ast.ForwardingAstVisitor; import lombok.ast.InlineIfExpression; import lombok.ast.IntegralLiteral; import lombok.ast.Literal; import lombok.ast.MethodInvocation; import lombok.ast.Node; import lombok.ast.NullLiteral; import lombok.ast.VariableReference; import java.util.ArrayList; import java.util.List; public class MathUtilsDetector extends Detector implements Detector.JavaScanner { public static final Issue ISSUE_FLOAT_EQUALS = Issue.create( "com.waz.FloatEquals", "Use `MathUtils#floatEqual()` instead of comparing two floats directly", "See http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html for more information.", Category.CORRECTNESS, 9, Severity.ERROR, new Implementation(MathUtilsDetector.class, Scope.JAVA_FILE_SCOPE)); public MathUtilsDetector() { } @Override public Speed getSpeed() { return Speed.FAST; } @Override public List<Class<? extends Node>> getApplicableNodeTypes() { List<Class<? extends Node>> nodes = new ArrayList<>(); nodes.add(BinaryExpression.class); return nodes; } @Override public AstVisitor createJavaVisitor(@NonNull JavaContext context) { if (!context.getProject().getReportIssues()) { return null; } return new FloatEqualVisitor(context); } private class FloatEqualVisitor extends ForwardingAstVisitor { private JavaContext context; FloatEqualVisitor(JavaContext context) { this.context = context; } @Override public boolean visitBinaryExpression(BinaryExpression node) { if (node == null) { return false; } if (!containsEqualityCheck(node)) { return false; } if (verifyExpression(node)) { context.report(ISSUE_FLOAT_EQUALS, context.getLocation(node), "Replace this comparison with `MathUtils#floatEqual()`"); } return true; } private boolean containsEqualityCheck(BinaryExpression expression) { BinaryOperator operator = expression.astOperator(); if (operator == BinaryOperator.EQUALS || operator == BinaryOperator.NOT_EQUALS) { return true; } for (Node node : expression.getChildren()) { if (!(node instanceof BinaryExpression)) { continue; } operator = ((BinaryExpression) node).astOperator(); if (operator != BinaryOperator.EQUALS && operator != BinaryOperator.NOT_EQUALS) { continue; } return true; } return false; } private boolean verifyExpression(@NonNull BinaryExpression node) { final BinaryOperator operator = node.astOperator(); if (operator != BinaryOperator.EQUALS && operator != BinaryOperator.NOT_EQUALS) { return false; } final Expression leftExpression = node.astLeft(); final Expression rightExpression = node.astRight(); final Literal leftType = getType(leftExpression); final Literal rightType = getType(rightExpression); if (leftType instanceof FloatingPointLiteral && rightType instanceof FloatingPointLiteral) { return true; } if (leftType instanceof FloatingPointLiteral && rightType instanceof IntegralLiteral) { return true; } return rightType instanceof FloatingPointLiteral && leftType instanceof IntegralLiteral; } private Literal getType(Expression expression) { if (expression instanceof Literal) { return (Literal) expression; } if (expression instanceof VariableReference || expression instanceof MethodInvocation) { JavaParser.ResolvedNode resolved = context.resolve(expression); JavaParser.TypeDescriptor descriptor; if (resolved instanceof JavaParser.ResolvedField) { JavaParser.ResolvedField field = (JavaParser.ResolvedField) resolved; descriptor = field.getType(); } else if (resolved instanceof JavaParser.ResolvedVariable) { JavaParser.ResolvedVariable field = (JavaParser.ResolvedVariable) resolved; descriptor = field.getType(); } else if (resolved instanceof JavaParser.ResolvedMethod) { descriptor = ((JavaParser.ResolvedMethod) resolved).getReturnType(); } else { return new NullLiteral(); } String type = descriptor.getName(); Literal x = getLiteralForType(type); if (x != null) { return x; } } if (expression instanceof InlineIfExpression) { return getType(((InlineIfExpression) expression).astIfTrue()); } if (expression instanceof BinaryExpression) { return getType(((BinaryExpression) expression).astLeft()); } return new NullLiteral(); } private Literal getLiteralForType(String type) { if ("float".equals(type) || "java.lang.Float".equals(type)) { return new FloatingPointLiteral(); } if ("double".equals(type) || "java.lang.Double".equals(type)) { return new FloatingPointLiteral(); } if ("int".equals(type) || "java.lang.Integer".equals(type)) { return new IntegralLiteral(); } if ("long".equals(type) || "java.lang.Long".equals(type)) { return new IntegralLiteral(); } return null; } } }