void submitPasses( @NotNull Map<FileEditor, HighlightingPass[]> passesMap, @NotNull DaemonProgressIndicator updateProgress) { if (isDisposed()) return; // null keys are ok MultiMap<Document, FileEditor> documentToEditors = MultiMap.createSet(); MultiMap<FileEditor, TextEditorHighlightingPass> documentBoundPasses = MultiMap.createSmart(); MultiMap<FileEditor, EditorBoundHighlightingPass> editorBoundPasses = MultiMap.createSmart(); List<Pair<FileEditor, TextEditorHighlightingPass>> passesWithNoDocuments = new ArrayList<>(); Set<VirtualFile> vFiles = new HashSet<>(); for (Map.Entry<FileEditor, HighlightingPass[]> entry : passesMap.entrySet()) { FileEditor fileEditor = entry.getKey(); HighlightingPass[] passes = entry.getValue(); Document document; if (fileEditor instanceof TextEditor) { Editor editor = ((TextEditor) fileEditor).getEditor(); LOG.assertTrue(!(editor instanceof EditorWindow)); document = editor.getDocument(); } else { VirtualFile virtualFile = ((FileEditorManagerEx) FileEditorManager.getInstance(myProject)).getFile(fileEditor); document = virtualFile == null ? null : FileDocumentManager.getInstance().getDocument(virtualFile); } if (document != null) { vFiles.add(FileDocumentManager.getInstance().getFile(document)); } int prevId = 0; for (final HighlightingPass pass : passes) { if (pass instanceof EditorBoundHighlightingPass) { EditorBoundHighlightingPass editorPass = (EditorBoundHighlightingPass) pass; editorPass.setId( nextPassId.incrementAndGet()); // have to make ids unique for this document editorBoundPasses.putValue(fileEditor, editorPass); } else { TextEditorHighlightingPass textEditorHighlightingPass = convertToTextHighlightingPass(pass, document, nextPassId, prevId); document = textEditorHighlightingPass.getDocument(); documentBoundPasses.putValue(fileEditor, textEditorHighlightingPass); if (document == null) { passesWithNoDocuments.add(Pair.create(fileEditor, textEditorHighlightingPass)); } else { documentToEditors.putValue(document, fileEditor); } prevId = textEditorHighlightingPass.getId(); } } } List<ScheduledPass> freePasses = new ArrayList<>(documentToEditors.size() * 5); List<ScheduledPass> dependentPasses = new ArrayList<>(documentToEditors.size() * 10); // (fileEditor, passId) -> created pass Map<Pair<FileEditor, Integer>, ScheduledPass> toBeSubmitted = new THashMap<>(passesMap.size()); final AtomicInteger threadsToStartCountdown = new AtomicInteger(0); for (Map.Entry<Document, Collection<FileEditor>> entry : documentToEditors.entrySet()) { Collection<FileEditor> fileEditors = entry.getValue(); Document document = entry.getKey(); FileEditor preferredFileEditor = getPreferredFileEditor(document, fileEditors); List<TextEditorHighlightingPass> passes = (List<TextEditorHighlightingPass>) documentBoundPasses.get(preferredFileEditor); if (passes.isEmpty()) { continue; } sortById(passes); for (TextEditorHighlightingPass currentPass : passes) { createScheduledPass( preferredFileEditor, currentPass, toBeSubmitted, passes, freePasses, dependentPasses, updateProgress, threadsToStartCountdown); } } for (Map.Entry<FileEditor, Collection<EditorBoundHighlightingPass>> entry : editorBoundPasses.entrySet()) { FileEditor fileEditor = entry.getKey(); Collection<EditorBoundHighlightingPass> createdEditorBoundPasses = entry.getValue(); List<TextEditorHighlightingPass> createdDocumentBoundPasses = (List<TextEditorHighlightingPass>) documentBoundPasses.get(fileEditor); List<TextEditorHighlightingPass> allCreatedPasses = new ArrayList<>(createdDocumentBoundPasses); allCreatedPasses.addAll(createdEditorBoundPasses); for (EditorBoundHighlightingPass pass : createdEditorBoundPasses) { createScheduledPass( fileEditor, pass, toBeSubmitted, allCreatedPasses, freePasses, dependentPasses, updateProgress, threadsToStartCountdown); } } for (Pair<FileEditor, TextEditorHighlightingPass> pair : passesWithNoDocuments) { FileEditor fileEditor = pair.first; TextEditorHighlightingPass pass = pair.second; createScheduledPass( fileEditor, pass, toBeSubmitted, ContainerUtil.emptyList(), freePasses, dependentPasses, updateProgress, threadsToStartCountdown); } if (CHECK_CONSISTENCY && !ApplicationInfoImpl.isInPerformanceTest()) { assertConsistency(freePasses, toBeSubmitted, threadsToStartCountdown); } log( updateProgress, null, vFiles + " ----- starting " + threadsToStartCountdown.get(), freePasses); for (ScheduledPass dependentPass : dependentPasses) { mySubmittedPasses.put(dependentPass, Job.NULL_JOB); } for (ScheduledPass freePass : freePasses) { submit(freePass); } }
private static class RootInfo { // getDirectoriesByPackageName used to be in this order, some clients might rely on that @NotNull final LinkedHashSet<VirtualFile> classAndSourceRoots = ContainerUtil.newLinkedHashSet(); @NotNull final Set<VirtualFile> libraryOrSdkSources = ContainerUtil.newHashSet(); @NotNull final Set<VirtualFile> libraryOrSdkClasses = ContainerUtil.newHashSet(); @NotNull final Map<VirtualFile, Module> contentRootOf = ContainerUtil.newHashMap(); @NotNull final MultiMap<VirtualFile, Module> sourceRootOf = MultiMap.createSet(); @NotNull final TObjectIntHashMap<VirtualFile> rootTypeId = new TObjectIntHashMap<VirtualFile>(); @NotNull final MultiMap<VirtualFile, Library> excludedFromLibraries = MultiMap.createSmart(); @NotNull final MultiMap<VirtualFile, Library> classOfLibraries = MultiMap.createSmart(); @NotNull final MultiMap<VirtualFile, Library> sourceOfLibraries = MultiMap.createSmart(); @NotNull final Set<VirtualFile> excludedFromProject = ContainerUtil.newHashSet(); @NotNull final Map<VirtualFile, Module> excludedFromModule = ContainerUtil.newHashMap(); @NotNull final Map<VirtualFile, String> packagePrefix = ContainerUtil.newHashMap(); @NotNull Set<VirtualFile> getAllRoots() { LinkedHashSet<VirtualFile> result = ContainerUtil.newLinkedHashSet(); result.addAll(classAndSourceRoots); result.addAll(contentRootOf.keySet()); result.addAll(excludedFromLibraries.keySet()); result.addAll(excludedFromModule.keySet()); result.addAll(excludedFromProject); return result; } @Nullable private VirtualFile findModuleRootInfo(@NotNull List<VirtualFile> hierarchy) { for (VirtualFile root : hierarchy) { Module module = contentRootOf.get(root); Module excludedFrom = excludedFromModule.get(root); if (module != null && excludedFrom != module) { return root; } if (excludedFrom != null || excludedFromProject.contains(root)) { return null; } } return null; } @Nullable private VirtualFile findNearestContentRootForExcluded(@NotNull List<VirtualFile> hierarchy) { for (VirtualFile root : hierarchy) { if (contentRootOf.containsKey(root)) { return root; } } return null; } @Nullable private VirtualFile findLibraryRootInfo(@NotNull List<VirtualFile> hierarchy, boolean source) { Set<Library> librariesToIgnore = ContainerUtil.newHashSet(); for (VirtualFile root : hierarchy) { librariesToIgnore.addAll(excludedFromLibraries.get(root)); if (source && libraryOrSdkSources.contains(root) && (!sourceOfLibraries.containsKey(root) || !librariesToIgnore.containsAll(sourceOfLibraries.get(root)))) { return root; } else if (!source && libraryOrSdkClasses.contains(root) && (!classOfLibraries.containsKey(root) || !librariesToIgnore.containsAll(classOfLibraries.get(root)))) { return root; } } return null; } private String calcPackagePrefix( @NotNull VirtualFile root, @NotNull List<VirtualFile> hierarchy, VirtualFile moduleContentRoot, VirtualFile libraryClassRoot, VirtualFile librarySourceRoot) { VirtualFile packageRoot = findPackageRootInfo(hierarchy, moduleContentRoot, libraryClassRoot, librarySourceRoot); String prefix = packagePrefix.get(packageRoot); if (prefix != null && !root.equals(packageRoot)) { assert packageRoot != null; String relative = VfsUtilCore.getRelativePath(root, packageRoot, '.'); prefix = StringUtil.isEmpty(prefix) ? relative : prefix + '.' + relative; } return prefix; } @Nullable private VirtualFile findPackageRootInfo( @NotNull List<VirtualFile> hierarchy, VirtualFile moduleContentRoot, VirtualFile libraryClassRoot, VirtualFile librarySourceRoot) { for (VirtualFile root : hierarchy) { if (moduleContentRoot != null && sourceRootOf.get(root).contains(contentRootOf.get(moduleContentRoot)) && librarySourceRoot == null) { return root; } if (root.equals(libraryClassRoot) || root.equals(librarySourceRoot)) { return root; } if (root.equals(moduleContentRoot) && !sourceRootOf.containsKey(root) && librarySourceRoot == null && libraryClassRoot == null) { return null; } } return null; } @NotNull private LinkedHashSet<OrderEntry> getLibraryOrderEntries( @NotNull List<VirtualFile> hierarchy, @Nullable VirtualFile libraryClassRoot, @Nullable VirtualFile librarySourceRoot, @NotNull MultiMap<VirtualFile, OrderEntry> libClassRootEntries, @NotNull MultiMap<VirtualFile, OrderEntry> libSourceRootEntries) { LinkedHashSet<OrderEntry> orderEntries = ContainerUtil.newLinkedHashSet(); for (VirtualFile root : hierarchy) { if (root.equals(libraryClassRoot) && !sourceRootOf.containsKey(root)) { orderEntries.addAll(libClassRootEntries.get(root)); } if (root.equals(librarySourceRoot) && libraryClassRoot == null) { orderEntries.addAll(libSourceRootEntries.get(root)); } if (libClassRootEntries.containsKey(root) || sourceRootOf.containsKey(root) && librarySourceRoot == null) { break; } } return orderEntries; } @Nullable private ModuleSourceOrderEntry getModuleSourceEntry( @NotNull List<VirtualFile> hierarchy, @NotNull VirtualFile moduleContentRoot, @NotNull MultiMap<VirtualFile, OrderEntry> libClassRootEntries) { Module module = contentRootOf.get(moduleContentRoot); for (VirtualFile root : hierarchy) { if (sourceRootOf.get(root).contains(module)) { return ContainerUtil.findInstance( ModuleRootManager.getInstance(module).getOrderEntries(), ModuleSourceOrderEntry.class); } if (libClassRootEntries.containsKey(root)) { return null; } } return null; } }
/** @author peter */ public class StandardInstructionVisitor extends InstructionVisitor { private static final Object ANY_VALUE = new Object(); private final Set<BinopInstruction> myReachable = new THashSet<BinopInstruction>(); private final Set<BinopInstruction> myCanBeNullInInstanceof = new THashSet<BinopInstruction>(); private final MultiMap<PushInstruction, Object> myPossibleVariableValues = MultiMap.createSet(); private final Set<PsiElement> myNotToReportReachability = new THashSet<PsiElement>(); private final Set<InstanceofInstruction> myUsefulInstanceofs = new THashSet<InstanceofInstruction>(); @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final FactoryMap<MethodCallInstruction, Nullness> myReturnTypeNullability = new FactoryMap<MethodCallInstruction, Nullness>() { @Override protected Nullness create(MethodCallInstruction key) { final PsiCallExpression callExpression = key.getCallExpression(); if (callExpression instanceof PsiNewExpression) { return Nullness.NOT_NULL; } return callExpression != null ? DfaPsiUtil.getElementNullability( key.getResultType(), callExpression.resolveMethod()) : null; } }; @Override public DfaInstructionState[] visitAssign( AssignInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { DfaValue dfaSource = memState.pop(); DfaValue dfaDest = memState.pop(); if (dfaDest instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue) dfaDest; DfaValueFactory factory = runner.getFactory(); if (dfaSource instanceof DfaVariableValue && factory.getVarFactory().getAllQualifiedBy(var).contains(dfaSource)) { Nullness nullability = memState.isNotNull(dfaSource) ? Nullness.NOT_NULL : ((DfaVariableValue) dfaSource).getInherentNullability(); dfaSource = factory.createTypeValue(((DfaVariableValue) dfaSource).getVariableType(), nullability); } if (var.getInherentNullability() == Nullness.NOT_NULL) { checkNotNullable( memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression()); } final PsiModifierListOwner psi = var.getPsiVariable(); if (!(psi instanceof PsiField) || !psi.hasModifierProperty(PsiModifier.VOLATILE)) { memState.setVarValue(var, dfaSource); } } else if (dfaDest instanceof DfaTypeValue && ((DfaTypeValue) dfaDest).isNotNull()) { checkNotNullable( memState, dfaSource, NullabilityProblem.assigningToNotNull, instruction.getRExpression()); } memState.push(dfaDest); return nextInstruction(instruction, runner, memState); } @Override public DfaInstructionState[] visitCheckReturnValue( CheckReturnValueInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final DfaValue retValue = memState.pop(); checkNotNullable( memState, retValue, NullabilityProblem.nullableReturn, instruction.getReturn()); return nextInstruction(instruction, runner, memState); } @Override public DfaInstructionState[] visitFieldReference( FieldReferenceInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final DfaValue qualifier = memState.pop(); if (!checkNotNullable( memState, qualifier, NullabilityProblem.fieldAccessNPE, instruction.getElementToAssert())) { forceNotNull(runner, memState, qualifier); } return nextInstruction(instruction, runner, memState); } @Override public DfaInstructionState[] visitPush( PushInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { if (instruction.isReferenceRead()) { DfaValue dfaValue = instruction.getValue(); if (dfaValue instanceof DfaVariableValue) { DfaConstValue constValue = memState.getConstantValue((DfaVariableValue) dfaValue); myPossibleVariableValues.putValue( instruction, constValue != null && (constValue.getValue() == null || constValue.getValue() instanceof Boolean) ? constValue : ANY_VALUE); } } return super.visitPush(instruction, runner, memState); } public List<Pair<PsiReferenceExpression, DfaConstValue>> getConstantReferenceValues() { List<Pair<PsiReferenceExpression, DfaConstValue>> result = ContainerUtil.newArrayList(); for (PushInstruction instruction : myPossibleVariableValues.keySet()) { Collection<Object> values = myPossibleVariableValues.get(instruction); if (values.size() == 1) { Object singleValue = values.iterator().next(); if (singleValue != ANY_VALUE) { result.add( Pair.create( (PsiReferenceExpression) instruction.getPlace(), (DfaConstValue) singleValue)); } } } return result; } @Override public DfaInstructionState[] visitTypeCast( TypeCastInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final DfaValueFactory factory = runner.getFactory(); DfaValue dfaExpr = factory.createValue(instruction.getCasted()); if (dfaExpr != null) { DfaTypeValue dfaType = (DfaTypeValue) factory.createTypeValue(instruction.getCastTo(), Nullness.UNKNOWN); DfaRelationValue dfaInstanceof = factory.getRelationFactory().createRelation(dfaExpr, dfaType, INSTANCEOF_KEYWORD, false); if (dfaInstanceof != null && !memState.applyInstanceofOrNull(dfaInstanceof)) { onInstructionProducesCCE(instruction); } } if (instruction.getCastTo() instanceof PsiPrimitiveType) { memState.push(runner.getFactory().getBoxedFactory().createUnboxed(memState.pop())); } return nextInstruction(instruction, runner, memState); } protected void onInstructionProducesCCE(TypeCastInstruction instruction) {} @Override public DfaInstructionState[] visitMethodCall( final MethodCallInstruction instruction, final DataFlowRunner runner, final DfaMemoryState memState) { DfaValue[] argValues = popCallArguments(instruction, runner, memState); final DfaValue qualifier = popQualifier(instruction, runner, memState); List<DfaMemoryState> currentStates = ContainerUtil.newArrayList(memState); Set<DfaMemoryState> finalStates = ContainerUtil.newLinkedHashSet(); if (argValues != null) { for (MethodContract contract : instruction.getContracts()) { currentStates = addContractResults( argValues, contract, currentStates, instruction, runner.getFactory(), finalStates); } } for (DfaMemoryState state : currentStates) { state.push(getMethodResultValue(instruction, qualifier, runner.getFactory())); finalStates.add(state); } return ContainerUtil.map2Array( finalStates, DfaInstructionState.class, new Function<DfaMemoryState, DfaInstructionState>() { @Override public DfaInstructionState fun(DfaMemoryState state) { if (instruction.shouldFlushFields()) { state.flushFields(); } return new DfaInstructionState( runner.getInstruction(instruction.getIndex() + 1), state); } }); } @Nullable private DfaValue[] popCallArguments( MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { final PsiExpression[] args = instruction.getArgs(); PsiMethod method = instruction.getTargetMethod(); boolean varargCall = instruction.isVarArgCall(); DfaValue[] argValues; if (method == null || instruction.getContracts().isEmpty()) { argValues = null; } else { int paramCount = method.getParameterList().getParametersCount(); if (paramCount == args.length || method.isVarArgs() && args.length >= paramCount - 1) { argValues = new DfaValue[paramCount]; if (varargCall) { argValues[paramCount - 1] = DfaUnknownValue.getInstance(); } } else { argValues = null; } } for (int i = 0; i < args.length; i++) { final DfaValue arg = memState.pop(); int paramIndex = args.length - i - 1; if (argValues != null && (paramIndex < argValues.length - 1 || !varargCall)) { argValues[paramIndex] = arg; } PsiExpression expr = args[paramIndex]; Nullness requiredNullability = instruction.getArgRequiredNullability(expr); if (requiredNullability == Nullness.NOT_NULL) { if (!checkNotNullable( memState, arg, NullabilityProblem.passingNullableToNotNullParameter, expr)) { forceNotNull(runner, memState, arg); } } else if (requiredNullability == Nullness.UNKNOWN) { checkNotNullable( memState, arg, NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter, expr); } } return argValues; } private DfaValue popQualifier( MethodCallInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { @NotNull final DfaValue qualifier = memState.pop(); boolean unboxing = instruction.getMethodType() == MethodCallInstruction.MethodType.UNBOXING; NullabilityProblem problem = unboxing ? NullabilityProblem.unboxingNullable : NullabilityProblem.callNPE; PsiExpression anchor = unboxing ? instruction.getContext() : instruction.getCallExpression(); if (!checkNotNullable(memState, qualifier, problem, anchor)) { forceNotNull(runner, memState, qualifier); } return qualifier; } private List<DfaMemoryState> addContractResults( DfaValue[] argValues, MethodContract contract, List<DfaMemoryState> states, MethodCallInstruction instruction, DfaValueFactory factory, Set<DfaMemoryState> finalStates) { DfaConstValue.Factory constFactory = factory.getConstFactory(); List<DfaMemoryState> falseStates = ContainerUtil.newArrayList(); for (int i = 0; i < argValues.length; i++) { DfaValue argValue = argValues[i]; MethodContract.ValueConstraint constraint = contract.arguments[i]; DfaConstValue expectedValue = constraint.getComparisonValue(factory); if (expectedValue == null) continue; boolean invertCondition = constraint.shouldUseNonEqComparison(); DfaValue condition = factory .getRelationFactory() .createRelation(argValue, expectedValue, EQEQ, invertCondition); if (condition == null) { if (!(argValue instanceof DfaConstValue)) { for (DfaMemoryState state : states) { falseStates.add(state.createCopy()); } continue; } condition = constFactory.createFromValue( (argValue == expectedValue) != invertCondition, PsiType.BOOLEAN, null); } List<DfaMemoryState> nextStates = ContainerUtil.newArrayList(); for (DfaMemoryState state : states) { boolean unknownVsNull = expectedValue == constFactory.getNull() && argValue instanceof DfaVariableValue && ((DfaMemoryStateImpl) state) .getVariableState((DfaVariableValue) argValue) .getNullability() == Nullness.UNKNOWN; DfaMemoryState falseCopy = state.createCopy(); if (state.applyCondition(condition)) { if (unknownVsNull && !invertCondition) { state.markEphemeral(); } nextStates.add(state); } if (falseCopy.applyCondition(condition.createNegated())) { if (unknownVsNull && invertCondition) { falseCopy.markEphemeral(); } falseStates.add(falseCopy); } } states = nextStates; } for (DfaMemoryState state : states) { state.push(getDfaContractReturnValue(contract, instruction, factory)); finalStates.add(state); } return falseStates; } private DfaValue getDfaContractReturnValue( MethodContract contract, MethodCallInstruction instruction, DfaValueFactory factory) { switch (contract.returnValue) { case NULL_VALUE: return factory.getConstFactory().getNull(); case NOT_NULL_VALUE: return factory.createTypeValue(instruction.getResultType(), Nullness.NOT_NULL); case TRUE_VALUE: return factory.getConstFactory().getTrue(); case FALSE_VALUE: return factory.getConstFactory().getFalse(); case THROW_EXCEPTION: return factory.getConstFactory().getContractFail(); default: return getMethodResultValue(instruction, null, factory); } } private static void forceNotNull(DataFlowRunner runner, DfaMemoryState memState, DfaValue arg) { if (arg instanceof DfaVariableValue) { DfaVariableValue var = (DfaVariableValue) arg; memState.setVarValue( var, runner.getFactory().createTypeValue(var.getVariableType(), Nullness.NOT_NULL)); } } @NotNull private DfaValue getMethodResultValue( MethodCallInstruction instruction, @Nullable DfaValue qualifierValue, DfaValueFactory factory) { DfaValue precalculated = instruction.getPrecalculatedReturnValue(); if (precalculated != null) { return precalculated; } final PsiType type = instruction.getResultType(); final MethodCallInstruction.MethodType methodType = instruction.getMethodType(); if (methodType == MethodCallInstruction.MethodType.UNBOXING) { return factory.getBoxedFactory().createUnboxed(qualifierValue); } if (methodType == MethodCallInstruction.MethodType.BOXING) { DfaValue boxed = factory.getBoxedFactory().createBoxed(qualifierValue); return boxed == null ? factory.createTypeValue(type, Nullness.NOT_NULL) : boxed; } if (methodType == MethodCallInstruction.MethodType.CAST) { assert qualifierValue != null; if (qualifierValue instanceof DfaConstValue) { Object casted = TypeConversionUtil.computeCastTo(((DfaConstValue) qualifierValue).getValue(), type); return factory .getConstFactory() .createFromValue(casted, type, ((DfaConstValue) qualifierValue).getConstant()); } return qualifierValue; } if (type != null && (type instanceof PsiClassType || type.getArrayDimensions() > 0)) { Nullness nullability = myReturnTypeNullability.get(instruction); if (nullability == Nullness.UNKNOWN && factory.isUnknownMembersAreNullable()) { nullability = Nullness.NULLABLE; } return factory.createTypeValue(type, nullability); } return DfaUnknownValue.getInstance(); } protected boolean checkNotNullable( DfaMemoryState state, DfaValue value, NullabilityProblem problem, PsiElement anchor) { boolean notNullable = state.checkNotNullable(value); if (notNullable && problem != NullabilityProblem.passingNullableArgumentToNonAnnotatedParameter) { DfaValueFactory factory = ((DfaMemoryStateImpl) state).getFactory(); state.applyCondition( factory .getRelationFactory() .createRelation(value, factory.getConstFactory().getNull(), NE, false)); } return notNullable; } @Override public DfaInstructionState[] visitBinop( BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { myReachable.add(instruction); DfaValue dfaRight = memState.pop(); DfaValue dfaLeft = memState.pop(); final IElementType opSign = instruction.getOperationSign(); if (opSign != null) { DfaInstructionState[] states = handleConstantComparison(instruction, runner, memState, dfaRight, dfaLeft, opSign); if (states == null) { states = handleRelationBinop(instruction, runner, memState, dfaRight, dfaLeft); } if (states != null) { return states; } if (PLUS == opSign) { memState.push(instruction.getNonNullStringValue(runner.getFactory())); } else { if (instruction instanceof InstanceofInstruction) { handleInstanceof((InstanceofInstruction) instruction, dfaRight, dfaLeft); } memState.push(DfaUnknownValue.getInstance()); } } else { memState.push(DfaUnknownValue.getInstance()); } instruction.setTrueReachable(); // Not a branching instruction actually. instruction.setFalseReachable(); return nextInstruction(instruction, runner, memState); } @Nullable private DfaInstructionState[] handleRelationBinop( BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, DfaValue dfaRight, DfaValue dfaLeft) { DfaValueFactory factory = runner.getFactory(); final Instruction next = runner.getInstruction(instruction.getIndex() + 1); DfaRelationValue dfaRelation = factory .getRelationFactory() .createRelation(dfaLeft, dfaRight, instruction.getOperationSign(), false); if (dfaRelation == null) { return null; } myCanBeNullInInstanceof.add(instruction); ArrayList<DfaInstructionState> states = new ArrayList<DfaInstructionState>(); final DfaMemoryState trueCopy = memState.createCopy(); if (trueCopy.applyCondition(dfaRelation)) { trueCopy.push(factory.getConstFactory().getTrue()); instruction.setTrueReachable(); states.add(new DfaInstructionState(next, trueCopy)); } //noinspection UnnecessaryLocalVariable DfaMemoryState falseCopy = memState; if (falseCopy.applyCondition(dfaRelation.createNegated())) { falseCopy.push(factory.getConstFactory().getFalse()); instruction.setFalseReachable(); states.add(new DfaInstructionState(next, falseCopy)); if (instruction instanceof InstanceofInstruction && !falseCopy.isNull(dfaLeft)) { myUsefulInstanceofs.add((InstanceofInstruction) instruction); } } return states.toArray(new DfaInstructionState[states.size()]); } public void skipConstantConditionReporting(@Nullable PsiElement anchor) { ContainerUtil.addIfNotNull(myNotToReportReachability, anchor); } private void handleInstanceof( InstanceofInstruction instruction, DfaValue dfaRight, DfaValue dfaLeft) { if (dfaLeft instanceof DfaTypeValue && dfaRight instanceof DfaTypeValue) { if (!((DfaTypeValue) dfaLeft).isNotNull()) { myCanBeNullInInstanceof.add(instruction); } if (((DfaTypeValue) dfaRight) .getDfaType() .isAssignableFrom(((DfaTypeValue) dfaLeft).getDfaType())) { return; } } myUsefulInstanceofs.add(instruction); } @Nullable private static DfaInstructionState[] handleConstantComparison( BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, DfaValue dfaRight, DfaValue dfaLeft, IElementType opSign) { if (dfaRight instanceof DfaConstValue && dfaLeft instanceof DfaVariableValue) { Object value = ((DfaConstValue) dfaRight).getValue(); if (value instanceof Number) { DfaInstructionState[] result = checkComparingWithConstant( instruction, runner, memState, (DfaVariableValue) dfaLeft, opSign, ((Number) value).doubleValue()); if (result != null) { return result; } } } if (dfaRight instanceof DfaVariableValue && dfaLeft instanceof DfaConstValue) { return handleConstantComparison( instruction, runner, memState, dfaLeft, dfaRight, DfaRelationValue.getSymmetricOperation(opSign)); } if (EQEQ != opSign && NE != opSign) { return null; } if (dfaLeft instanceof DfaConstValue && dfaRight instanceof DfaConstValue || dfaLeft == runner.getFactory().getConstFactory().getContractFail() || dfaRight == runner.getFactory().getConstFactory().getContractFail()) { boolean negated = (NE == opSign) ^ (DfaMemoryStateImpl.isNaN(dfaLeft) || DfaMemoryStateImpl.isNaN(dfaRight)); if (dfaLeft == dfaRight ^ negated) { return alwaysTrue(instruction, runner, memState); } return alwaysFalse(instruction, runner, memState); } return null; } @Nullable private static DfaInstructionState[] checkComparingWithConstant( BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, DfaVariableValue var, IElementType opSign, double comparedWith) { DfaConstValue knownConstantValue = memState.getConstantValue(var); Object knownValue = knownConstantValue == null ? null : knownConstantValue.getValue(); if (knownValue instanceof Number) { double knownDouble = ((Number) knownValue).doubleValue(); return checkComparisonWithKnownRange( instruction, runner, memState, opSign, comparedWith, knownDouble, knownDouble); } PsiType varType = var.getVariableType(); if (!(varType instanceof PsiPrimitiveType)) return null; if (varType == PsiType.FLOAT || varType == PsiType.DOUBLE) return null; double minValue = varType == PsiType.BYTE ? Byte.MIN_VALUE : varType == PsiType.SHORT ? Short.MIN_VALUE : varType == PsiType.INT ? Integer.MIN_VALUE : varType == PsiType.CHAR ? Character.MIN_VALUE : Long.MIN_VALUE; double maxValue = varType == PsiType.BYTE ? Byte.MAX_VALUE : varType == PsiType.SHORT ? Short.MAX_VALUE : varType == PsiType.INT ? Integer.MAX_VALUE : varType == PsiType.CHAR ? Character.MAX_VALUE : Long.MAX_VALUE; return checkComparisonWithKnownRange( instruction, runner, memState, opSign, comparedWith, minValue, maxValue); } @Nullable private static DfaInstructionState[] checkComparisonWithKnownRange( BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState, IElementType opSign, double comparedWith, double rangeMin, double rangeMax) { if (comparedWith < rangeMin || comparedWith > rangeMax) { if (opSign == EQEQ) return alwaysFalse(instruction, runner, memState); if (opSign == NE) return alwaysTrue(instruction, runner, memState); } if (opSign == LT && comparedWith <= rangeMin) return alwaysFalse(instruction, runner, memState); if (opSign == LT && comparedWith > rangeMax) return alwaysTrue(instruction, runner, memState); if (opSign == LE && comparedWith >= rangeMax) return alwaysTrue(instruction, runner, memState); if (opSign == GT && comparedWith >= rangeMax) return alwaysFalse(instruction, runner, memState); if (opSign == GT && comparedWith < rangeMin) return alwaysTrue(instruction, runner, memState); if (opSign == GE && comparedWith <= rangeMin) return alwaysTrue(instruction, runner, memState); return null; } private static DfaInstructionState[] alwaysFalse( BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { memState.push(runner.getFactory().getConstFactory().getFalse()); instruction.setFalseReachable(); return nextInstruction(instruction, runner, memState); } private static DfaInstructionState[] alwaysTrue( BinopInstruction instruction, DataFlowRunner runner, DfaMemoryState memState) { memState.push(runner.getFactory().getConstFactory().getTrue()); instruction.setTrueReachable(); return nextInstruction(instruction, runner, memState); } public boolean isInstanceofRedundant(InstanceofInstruction instruction) { return !myUsefulInstanceofs.contains(instruction) && !instruction.isConditionConst() && myReachable.contains(instruction); } public boolean canBeNull(BinopInstruction instruction) { return myCanBeNullInInstanceof.contains(instruction); } public boolean silenceConstantCondition(@Nullable PsiElement element) { for (PsiElement skipped : myNotToReportReachability) { if (PsiTreeUtil.isAncestor(element, skipped, false)) { return true; } } if (PsiTreeUtil.findChildOfType(element, PsiAssignmentExpression.class) != null) { return true; } return false; } }
@NotNull final RunnerResult analyzeMethod( @NotNull PsiElement psiBlock, @NotNull InstructionVisitor visitor, boolean ignoreAssertions, @NotNull Collection<DfaMemoryState> initialStates) { if (PsiTreeUtil.findChildOfType(psiBlock, OuterLanguageElement.class) != null) return RunnerResult.NOT_APPLICABLE; try { final ControlFlow flow = new ControlFlowAnalyzer(myValueFactory, psiBlock, ignoreAssertions).buildControlFlow(); if (flow == null) return RunnerResult.NOT_APPLICABLE; int[] loopNumber = LoopAnalyzer.calcInLoop(flow); int endOffset = flow.getInstructionCount(); myInstructions = flow.getInstructions(); myNestedClosures.clear(); Set<Instruction> joinInstructions = ContainerUtil.newHashSet(); for (int index = 0; index < myInstructions.length; index++) { Instruction instruction = myInstructions[index]; if (instruction instanceof GotoInstruction) { joinInstructions.add(myInstructions[((GotoInstruction) instruction).getOffset()]); } else if (instruction instanceof ConditionalGotoInstruction) { joinInstructions.add( myInstructions[((ConditionalGotoInstruction) instruction).getOffset()]); } else if (instruction instanceof MethodCallInstruction && !((MethodCallInstruction) instruction).getContracts().isEmpty()) { joinInstructions.add(myInstructions[index + 1]); } } if (LOG.isDebugEnabled()) { LOG.debug("Analyzing code block: " + psiBlock.getText()); for (int i = 0; i < myInstructions.length; i++) { LOG.debug(i + ": " + myInstructions[i]); } } // for (int i = 0; i < myInstructions.length; i++) System.out.println(i + ": " + // myInstructions[i].toString()); Integer tooExpensiveHash = psiBlock.getUserData(TOO_EXPENSIVE_HASH); if (tooExpensiveHash != null && tooExpensiveHash == psiBlock.getText().hashCode()) { LOG.debug("Too complex because hasn't changed since being too complex already"); return RunnerResult.TOO_COMPLEX; } final StateQueue queue = new StateQueue(); for (final DfaMemoryState initialState : initialStates) { queue.offer(new DfaInstructionState(myInstructions[0], initialState)); } MultiMap<BranchingInstruction, DfaMemoryState> processedStates = MultiMap.createSet(); MultiMap<BranchingInstruction, DfaMemoryState> incomingStates = MultiMap.createSet(); long msLimit = shouldCheckTimeLimit() ? Registry.intValue("ide.dfa.time.limit.online") : Registry.intValue("ide.dfa.time.limit.offline"); WorkingTimeMeasurer measurer = new WorkingTimeMeasurer(msLimit * 1000 * 1000); int count = 0; while (!queue.isEmpty()) { List<DfaInstructionState> states = queue.getNextInstructionStates(joinInstructions); for (DfaInstructionState instructionState : states) { if (count++ % 1024 == 0 && measurer.isTimeOver()) { LOG.debug("Too complex because the analysis took too long"); psiBlock.putUserData(TOO_EXPENSIVE_HASH, psiBlock.getText().hashCode()); return RunnerResult.TOO_COMPLEX; } ProgressManager.checkCanceled(); if (LOG.isDebugEnabled()) { LOG.debug(instructionState.toString()); } // System.out.println(instructionState.toString()); Instruction instruction = instructionState.getInstruction(); if (instruction instanceof BranchingInstruction) { BranchingInstruction branching = (BranchingInstruction) instruction; Collection<DfaMemoryState> processed = processedStates.get(branching); if (processed.contains(instructionState.getMemoryState())) { continue; } if (processed.size() > MAX_STATES_PER_BRANCH) { LOG.debug("Too complex because too many different possible states"); return RunnerResult.TOO_COMPLEX; // Too complex :( } if (loopNumber[branching.getIndex()] != 0) { processedStates.putValue(branching, instructionState.getMemoryState().createCopy()); } } DfaInstructionState[] after = acceptInstruction(visitor, instructionState); for (DfaInstructionState state : after) { Instruction nextInstruction = state.getInstruction(); if (nextInstruction.getIndex() >= endOffset) { continue; } handleStepOutOfLoop( instruction, nextInstruction, loopNumber, processedStates, incomingStates, states, after, queue); if (nextInstruction instanceof BranchingInstruction) { BranchingInstruction branching = (BranchingInstruction) nextInstruction; if (processedStates.get(branching).contains(state.getMemoryState()) || incomingStates.get(branching).contains(state.getMemoryState())) { continue; } if (loopNumber[branching.getIndex()] != 0) { incomingStates.putValue(branching, state.getMemoryState().createCopy()); } } queue.offer(state); } } } psiBlock.putUserData(TOO_EXPENSIVE_HASH, null); LOG.debug("Analysis ok"); return RunnerResult.OK; } catch (ArrayIndexOutOfBoundsException e) { LOG.error(psiBlock.getText(), e); return RunnerResult.ABORTED; } catch (EmptyStackException e) { LOG.error(psiBlock.getText(), e); return RunnerResult.ABORTED; } }