/* * Copyright 2016 Google Inc. All Rights Reserved. * * 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.google.errorprone.bugpatterns.android; import static com.google.errorprone.BugPattern.Category.ANDROID; import static com.google.errorprone.BugPattern.SeverityLevel.WARNING; import static com.sun.source.tree.Tree.Kind.STRING_LITERAL; import com.google.common.collect.ImmutableMap; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.bugpatterns.BugChecker.LiteralTreeMatcher; import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.matchers.Description; import com.google.errorprone.util.ASTHelpers; import com.sun.source.tree.LiteralTree; import java.util.Map; /** * TODO(avenet): Restrict this check to Android code once the capability is available in Error * Prone. See b/27967984. * * @author avenet@google.com (Arnaud J. Venet) */ @BugPattern( name = "HardCodedSdCardPath", altNames = {"SdCardPath"}, summary = "Hardcoded reference to /sdcard", category = ANDROID, severity = WARNING ) public class HardCodedSdCardPath extends BugChecker implements LiteralTreeMatcher { // The proper ways of retrieving the "/sdcard" and "/data/data" directories. static final String SDCARD = "Environment.getExternalStorageDirectory().getPath()"; static final String DATA = "Context.getFilesDir().getPath()"; // Maps each platform-dependent way of accessing "/sdcard" or "/data/data" to its // portable equivalent. static final ImmutableMap<String, String> PATH_TABLE = new ImmutableMap.Builder<String, String>() .put("/sdcard", SDCARD) .put("/mnt/sdcard", SDCARD) .put("/system/media/sdcard", SDCARD) .put("file://sdcard", SDCARD) .put("file:///sdcard", SDCARD) .put("/data/data", DATA) .put("/data/user", DATA) .build(); @Override public Description matchLiteral(LiteralTree tree, VisitorState state) { if (tree.getKind() != STRING_LITERAL) { return Description.NO_MATCH; } // Hard-coded paths may come handy when writing tests. Therefore, we suppress the check // for code located under 'javatests'. if (ASTHelpers.isJUnitTestCode(state)) { return Description.NO_MATCH; } String literal = (String) tree.getValue(); if (literal == null) { return Description.NO_MATCH; } for (Map.Entry<String, String> entry : PATH_TABLE.entrySet()) { String hardCodedPath = entry.getKey(); if (!literal.startsWith(hardCodedPath)) { continue; } String correctPath = entry.getValue(); String remainderPath = literal.substring(hardCodedPath.length()); // Replace the hard-coded fragment of the path with a portable expression. SuggestedFix.Builder suggestedFix = SuggestedFix.builder(); if (remainderPath.isEmpty()) { suggestedFix.replace(tree, correctPath); } else { suggestedFix.replace(tree, correctPath + " + \"" + remainderPath + "\""); } // Add the corresponding import statements. if (correctPath.equals(SDCARD)) { suggestedFix.addImport("android.os.Environment"); } else { suggestedFix.addImport("android.content.Context"); } return describeMatch(tree, suggestedFix.build()); } return Description.NO_MATCH; } }