/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.klint.checks; import static com.android.SdkConstants.CLASS_INTENT; import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION; import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_READ; import static com.android.tools.klint.checks.SupportAnnotationDetector.PERMISSION_ANNOTATION_WRITE; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.klint.client.api.UastLintUtils; import com.android.tools.klint.detector.api.JavaContext; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiVariable; import org.jetbrains.uast.*; import org.jetbrains.uast.util.UastExpressionUtils; import java.util.List; /** * Utility for locating permissions required by an intent or content resolver */ public class PermissionFinder { /** * Operation that has a permission requirement -- such as a method call, * a content resolver read or write operation, an intent, etc. */ public enum Operation { CALL, ACTION, READ, WRITE; /** Prefix to use when describing a name with a permission requirement */ public String prefix() { switch (this) { case ACTION: return "by intent"; case READ: return "to read"; case WRITE: return "to write"; case CALL: default: return "by"; } } } /** A permission requirement given a name and operation */ public static class Result { @NonNull public final PermissionRequirement requirement; @NonNull public final String name; @NonNull public final Operation operation; public Result( @NonNull Operation operation, @NonNull PermissionRequirement requirement, @NonNull String name) { this.operation = operation; this.requirement = requirement; this.name = name; } } /** * Searches for a permission requirement for the given parameter in the given call * * @param operation the operation to look up * @param context the context to use for lookup * @param parameter the parameter which contains the value which implies the permission * @return the result with the permission requirement, or null if nothing is found */ @Nullable public static Result findRequiredPermissions( @NonNull Operation operation, @NonNull JavaContext context, @NonNull UElement parameter) { // To find the permission required by an intent, we proceed in 3 steps: // (1) Locate the parameter in the start call that corresponds to // the Intent // // (2) Find the place where the intent is initialized, and figure // out the action name being passed to it. // // (3) Find the place where the action is defined, and look for permission // annotations on that action declaration! return new PermissionFinder(context, operation).search(parameter); } private PermissionFinder(@NonNull JavaContext context, @NonNull Operation operation) { mContext = context; mOperation = operation; } @NonNull private final JavaContext mContext; @NonNull private final Operation mOperation; @Nullable public Result search(@NonNull UElement node) { if (UastLiteralUtils.isNullLiteral(node)) { return null; } else if (node instanceof UIfExpression) { UIfExpression expression = (UIfExpression) node; if (expression.getThenExpression() != null) { Result result = search(expression.getThenExpression()); if (result != null) { return result; } } if (expression.getElseExpression() != null) { Result result = search(expression.getElseExpression()); if (result != null) { return result; } } } else if (UastExpressionUtils.isTypeCast(node)) { UBinaryExpressionWithType cast = (UBinaryExpressionWithType) node; UExpression operand = cast.getOperand(); return search(operand); } else if (node instanceof UParenthesizedExpression) { UParenthesizedExpression parens = (UParenthesizedExpression) node; UExpression expression = parens.getExpression(); if (expression != null) { return search(expression); } } else if (UastExpressionUtils.isConstructorCall(node) && mOperation == Operation.ACTION) { // Identifies "new Intent(argument)" calls and, if found, continues // resolving the argument instead looking for the action definition UCallExpression call = (UCallExpression) node; UReferenceExpression classReference = call.getClassReference(); String type = classReference != null ? UastUtils.getQualifiedName(classReference) : null; if (CLASS_INTENT.equals(type)) { List<UExpression> expressions = call.getValueArguments(); if (!expressions.isEmpty()) { UExpression action = expressions.get(0); if (action != null) { return search(action); } } } return null; } else if (node instanceof UReferenceExpression) { PsiElement resolved = ((UReferenceExpression) node).resolve(); if (resolved instanceof PsiField) { UField field = (UField) mContext.getUastContext().convertElementWithParent(resolved, UField.class); if (field == null) { return null; } if (mOperation == Operation.ACTION) { UAnnotation annotation = field.findAnnotation(PERMISSION_ANNOTATION); if (annotation != null) { return getPermissionRequirement(field, annotation); } } else if (mOperation == Operation.READ || mOperation == Operation.WRITE) { String fqn = mOperation == Operation.READ ? PERMISSION_ANNOTATION_READ : PERMISSION_ANNOTATION_WRITE; UAnnotation annotation = field.findAnnotation(fqn); if (annotation != null) { List<UNamedExpression> attributes = annotation.getAttributeValues(); UNamedExpression o = attributes.size() == 1 ? attributes.get(0) : null; if (o != null && o.getExpression() instanceof UAnnotation) { annotation = (UAnnotation) o.getExpression(); if (PERMISSION_ANNOTATION.equals(annotation.getQualifiedName())) { return getPermissionRequirement(field, annotation); } } else { // The complex annotations used for read/write cannot be // expressed in the external annotations format, so they're inlined. // (See Extractor.AnnotationData#write). // // Instead we've inlined the fields of the annotation on the // outer one: return getPermissionRequirement(field, annotation); } } } else { assert false : mOperation; } } if (resolved instanceof PsiVariable) { PsiVariable variable = (PsiVariable) resolved; UExpression lastAssignment = UastLintUtils.findLastAssignment(variable, node, mContext); if (lastAssignment != null) { return search(lastAssignment); } } } return null; } @NonNull private Result getPermissionRequirement( @NonNull PsiField field, @NonNull UAnnotation annotation) { PermissionRequirement requirement = PermissionRequirement.create(annotation); PsiClass containingClass = field.getContainingClass(); String name = containingClass != null ? containingClass.getName() + "." + field.getName() : field.getName(); assert name != null; return new Result(mOperation, requirement, name); } }