Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (C) 2012 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.lint.checks;
import static com.android.SdkConstants.CONSTRUCTOR_NAME;
import static com.android.SdkConstants.FORMAT_METHOD;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Detector.ClassScanner;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.Location;
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 org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SourceInterpreter;
import org.objectweb.asm.tree.analysis.SourceValue;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Checks for errors related to locale handling
*/
public class LocaleDetector extends Detector implements ClassScanner {
private static final Implementation IMPLEMENTATION = new Implementation(
LocaleDetector.class,
Scope.CLASS_FILE_SCOPE);
/** Calling risky convenience methods */
public static final Issue STRING_LOCALE = Issue.create(
"DefaultLocale", //$NON-NLS-1$
"Implied default locale in case conversion",
"Calling `String#toLowerCase()` or `#toUpperCase()` *without specifying an " +
"explicit locale* is a common source of bugs. The reason for that is that those " +
"methods will use the current locale on the user's device, and even though the " +
"code appears to work correctly when you are developing the app, it will fail " +
"in some locales. For example, in the Turkish locale, the uppercase replacement " +
"for `i` is *not* `I`.\n" +
"\n" +
"If you want the methods to just perform ASCII replacement, for example to convert " +
"an enum name, call `String#toUpperCase(Locale.US)` instead. If you really want to " +
"use the current locale, call `String#toUpperCase(Locale.getDefault())` instead.",
Category.CORRECTNESS,
6,
Severity.WARNING,
IMPLEMENTATION)
.addMoreInfo(
"http://developer.android.com/reference/java/util/Locale.html#default_locale"); //$NON-NLS-1$
/** Constructing SimpleDateFormat without an explicit locale */
public static final Issue DATE_FORMAT = Issue.create(
"SimpleDateFormat", //$NON-NLS-1$
"Implied locale in date format",
"Almost all callers should use `getDateInstance()`, `getDateTimeInstance()`, or " +
"`getTimeInstance()` to get a ready-made instance of SimpleDateFormat suitable " +
"for the user's locale. The main reason you'd create an instance this class " +
"directly is because you need to format/parse a specific machine-readable format, " +
"in which case you almost certainly want to explicitly ask for US to ensure that " +
"you get ASCII digits (rather than, say, Arabic digits).\n" +
"\n" +
"Therefore, you should either use the form of the SimpleDateFormat constructor " +
"where you pass in an explicit locale, such as Locale.US, or use one of the " +
"get instance methods, or suppress this error if really know what you are doing.",
Category.CORRECTNESS,
6,
Severity.WARNING,
IMPLEMENTATION)
.addMoreInfo(
"http://developer.android.com/reference/java/text/SimpleDateFormat.html"); //$NON-NLS-1$
static final String DATE_FORMAT_OWNER = "java/text/SimpleDateFormat"; //$NON-NLS-1$
private static final String STRING_OWNER = "java/lang/String"; //$NON-NLS-1$
/** Constructs a new {@link LocaleDetector} */
public LocaleDetector() {
}
@NonNull
@Override
public Speed getSpeed() {
return Speed.FAST;
}
// ---- Implements ClassScanner ----
@Override
@Nullable
public List getApplicableCallNames() {
return Arrays.asList(
"toLowerCase", //$NON-NLS-1$
"toUpperCase", //$NON-NLS-1$
FORMAT_METHOD
);
}
@Override
@Nullable
public List getApplicableCallOwners() {
return Collections.singletonList(DATE_FORMAT_OWNER);
}
@Override
public void checkCall(@NonNull ClassContext context, @NonNull ClassNode classNode,
@NonNull MethodNode method, @NonNull MethodInsnNode call) {
String owner = call.owner;
String desc = call.desc;
String name = call.name;
if (owner.equals(DATE_FORMAT_OWNER)) {
if (!name.equals(CONSTRUCTOR_NAME)) {
return;
}
if (desc.equals("(Ljava/lang/String;Ljava/text/DateFormatSymbols;)V") //$NON-NLS-1$
|| desc.equals("()V") //$NON-NLS-1$
|| desc.equals("(Ljava/lang/String;)V")) { //$NON-NLS-1$
Location location = context.getLocation(call);
String message =
"To get local formatting use `getDateInstance()`, `getDateTimeInstance()`, " +
"or `getTimeInstance()`, or use `new SimpleDateFormat(String template, " +
"Locale locale)` with for example `Locale.US` for ASCII dates.";
context.report(DATE_FORMAT, method, call, location, message);
}
return;
} else if (!owner.equals(STRING_OWNER)) {
return;
}
if (name.equals(FORMAT_METHOD)) {
// Only check the non-locale version of String.format
if (!desc.equals("(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;")) { //$NON-NLS-1$
return;
}
// Find the formatting string
Analyzer analyzer = new Analyzer(new SourceInterpreter() {
@Override
public SourceValue newOperation(AbstractInsnNode insn) {
if (insn.getOpcode() == Opcodes.LDC) {
Object cst = ((LdcInsnNode) insn).cst;
if (cst instanceof String) {
return new StringValue(1, (String) cst);
}
}
return super.newOperation(insn);
}
});
try {
Frame[] frames = analyzer.analyze(classNode.name, method);
InsnList instructions = method.instructions;
Frame frame = frames[instructions.indexOf(call)];
if (frame.getStackSize() == 0) {
return;
}
SourceValue stackValue = (SourceValue) frame.getStack(0);
if (stackValue instanceof StringValue) {
String format = ((StringValue) stackValue).getString();
if (format != null && StringFormatDetector.isLocaleSpecific(format)) {
Location location = context.getLocation(call);
String message =
"Implicitly using the default locale is a common source of bugs: " +
"Use `String.format(Locale, ...)` instead";
context.report(STRING_LOCALE, method, call, location, message);
}
}
} catch (AnalyzerException e) {
context.log(e, null);
}
} else {
if (desc.equals("()Ljava/lang/String;")) { //$NON-NLS-1$
Location location = context.getLocation(call);
String message = String.format(
"Implicitly using the default locale is a common source of bugs: " +
"Use `%1$s(Locale)` instead", name);
context.report(STRING_LOCALE, method, call, location, message);
}
}
}
private static class StringValue extends SourceValue {
private final String mString;
StringValue(int size, String string) {
super(size);
mString = string;
}
String getString() {
return mString;
}
@Override
public int getSize() {
return 1;
}
}
}