blob: f8ba8be90653607146190c8df4abeddf960d68e4 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2017 GK Software AG and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Stephan Herrmann - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.ast;
import org.eclipse.jdt.internal.compiler.lookup.BlockScope;
import org.eclipse.jdt.internal.compiler.lookup.ParameterizedTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeConstants.DangerousMethod;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;
/* @NonNullByDefault */
public class UnlikelyArgumentCheck {
public final DangerousMethod dangerousMethod;
public final TypeBinding typeToCheck;
public final TypeBinding expectedType;
public final TypeBinding typeToReport;
private UnlikelyArgumentCheck(DangerousMethod dangerousMethod, TypeBinding typeToCheck, TypeBinding expectedType,
TypeBinding typeToReport) {
this.dangerousMethod = dangerousMethod;
this.typeToCheck = typeToCheck;
this.expectedType = expectedType;
this.typeToReport = typeToReport;
}
/**
* Check if the invocation is likely a bug.
* @param currentScope
* @return false, if the typeToCheck does not seem to related to the expectedType
*/
public boolean isDangerous(BlockScope currentScope) {
TypeBinding typeToCheck2 = this.typeToCheck;
// take autoboxing into account
if (typeToCheck2.isBaseType()) {
typeToCheck2 = currentScope.boxing(typeToCheck2);
}
TypeBinding expectedType2 = this.expectedType;
if (expectedType2.isBaseType()) { // can happen for the first parameter of java.util.Object.equals
expectedType2 = currentScope.boxing(expectedType2);
}
if(this.dangerousMethod != DangerousMethod.Equals && currentScope.compilerOptions().reportUnlikelyCollectionMethodArgumentTypeStrict) {
return !typeToCheck2.isCompatibleWith(expectedType2, currentScope);
}
// unless both types are true type variables (not captures), take the erasure of both.
if (typeToCheck2.isCapture() || !typeToCheck2.isTypeVariable() || expectedType2.isCapture()
|| !expectedType2.isTypeVariable()) {
typeToCheck2 = typeToCheck2.erasure();
expectedType2 = expectedType2.erasure();
}
return !typeToCheck2.isCompatibleWith(expectedType2, currentScope)
&& !expectedType2.isCompatibleWith(typeToCheck2, currentScope);
}
/**
* When targeting a well-known dangerous method, returns an UnlikelyArgumentCheck object that contains the types to
* check against each other and to report
*/
public static /* @Nullable */ UnlikelyArgumentCheck determineCheckForNonStaticSingleArgumentMethod(
TypeBinding argumentType, Scope scope, char[] selector, TypeBinding actualReceiverType,
TypeBinding[] parameters) {
// detecting only methods with a single argument, typed either as Object or as Collection:
if (parameters.length != 1)
return null;
int paramTypeId = parameters[0].original().id;
if (paramTypeId != TypeIds.T_JavaLangObject && paramTypeId != TypeIds.T_JavaUtilCollection)
return null;
// check selectors before typeBits as to avoid unnecessary super-traversals for the receiver type
DangerousMethod suspect = DangerousMethod.detectSelector(selector);
if (suspect == null)
return null;
if (actualReceiverType.hasTypeBit(TypeIds.BitMap)) {
if (paramTypeId == TypeIds.T_JavaLangObject) {
switch (suspect) {
case ContainsKey:
case Get:
case Remove:
// map operations taking a key
ReferenceBinding mapType = actualReceiverType
.findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilMap, false);
if (mapType != null && mapType.isParameterizedType())
return new UnlikelyArgumentCheck(suspect, argumentType,
((ParameterizedTypeBinding) mapType).typeArguments()[0], mapType);
break;
case ContainsValue:
// map operation taking a value
mapType = actualReceiverType.findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilMap, false);
if (mapType != null && mapType.isParameterizedType())
return new UnlikelyArgumentCheck(suspect, argumentType,
((ParameterizedTypeBinding) mapType).typeArguments()[1], mapType);
break;
default: // no other suspects are detected in java.util.Map
}
}
}
if (actualReceiverType.hasTypeBit(TypeIds.BitCollection)) {
if (paramTypeId == TypeIds.T_JavaLangObject) {
switch (suspect) {
case Remove:
case Contains:
// collection operations taking a single element
ReferenceBinding collectionType = actualReceiverType
.findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilCollection, false);
if (collectionType != null && collectionType.isParameterizedType())
return new UnlikelyArgumentCheck(suspect, argumentType,
((ParameterizedTypeBinding) collectionType).typeArguments()[0], collectionType);
break;
default: // no other suspects with Object-parameter are detected in java.util.Collection
}
} else if (paramTypeId == TypeIds.T_JavaUtilCollection) {
switch (suspect) {
case RemoveAll:
case ContainsAll:
case RetainAll:
// collection operations taking another collection
ReferenceBinding collectionType = actualReceiverType
.findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilCollection, false);
ReferenceBinding argumentCollectionType = argumentType
.findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilCollection, false);
if (collectionType != null && argumentCollectionType != null
&& argumentCollectionType.isParameterizedTypeWithActualArguments()
&& collectionType.isParameterizedTypeWithActualArguments()) {
return new UnlikelyArgumentCheck(suspect,
((ParameterizedTypeBinding) argumentCollectionType).typeArguments()[0],
((ParameterizedTypeBinding) collectionType).typeArguments()[0], collectionType);
}
break;
default: // no other suspects with Collection-parameter are detected in java.util.Collection
}
}
if (actualReceiverType.hasTypeBit(TypeIds.BitList)) {
if (paramTypeId == TypeIds.T_JavaLangObject) {
switch (suspect) {
case IndexOf:
case LastIndexOf:
// list operations taking a single element
ReferenceBinding listType = actualReceiverType
.findSuperTypeOriginatingFrom(TypeIds.T_JavaUtilList, false);
if (listType != null && listType.isParameterizedType())
return new UnlikelyArgumentCheck(suspect, argumentType,
((ParameterizedTypeBinding) listType).typeArguments()[0], listType);
break;
default: // no other suspects are detected in java.util.List
}
}
}
}
if (paramTypeId == TypeIds.T_JavaLangObject && suspect == DangerousMethod.Equals) {
return new UnlikelyArgumentCheck(suspect, argumentType, actualReceiverType, actualReceiverType);
}
return null; // not replacing
}
public static /* @Nullable */ UnlikelyArgumentCheck determineCheckForStaticTwoArgumentMethod(
TypeBinding secondParameter, Scope scope, char[] selector, TypeBinding firstParameter,
TypeBinding[] parameters, TypeBinding actualReceiverType) {
// detecting only methods with two arguments, both typed as Object:
if (parameters.length != 2)
return null;
int paramTypeId1 = parameters[0].original().id;
int paramTypeId2 = parameters[1].original().id;
if (paramTypeId1 != TypeIds.T_JavaLangObject || paramTypeId2 != TypeIds.T_JavaLangObject)
return null;
// check selectors before typeBits as to avoid unnecessary super-traversals for the receiver type
DangerousMethod suspect = DangerousMethod.detectSelector(selector);
if (suspect == null)
return null;
if (actualReceiverType.id == TypeIds.T_JavaUtilObjects && suspect == DangerousMethod.Equals) {
return new UnlikelyArgumentCheck(suspect, secondParameter, firstParameter, firstParameter);
}
return null;
}
}