/******************************************************************************* * Copyright (c) 2015, 2017 GK Software AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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; } }