package org.mutabilitydetector.checkers; /* * #%L * MutabilityDetector * %% * Copyright (C) 2008 - 2014 Graham Allan * %% * 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. * #L% */ import com.google.classpath.ClassPath; import com.google.classpath.ClassPathFactory; import com.google.common.base.Optional; import org.mutabilitydetector.AnalysisError; import org.mutabilitydetector.AnalysisResult; import org.mutabilitydetector.asmoverride.AsmClassVisitor; import org.mutabilitydetector.locations.CodeLocation; import org.mutabilitydetector.locations.Dotted; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import java.io.IOException; import java.io.InputStream; import static java.lang.String.format; import static java.util.Collections.singleton; import static org.mutabilitydetector.MutabilityReason.CANNOT_ANALYSE; import static org.mutabilitydetector.MutableReasonDetail.newMutableReasonDetail; import static org.mutabilitydetector.checkers.CheckerRunner.ExceptionPolicy.FAIL_FAST; public final class CheckerRunner { private final ClassPath classpath; private final UnhandledExceptionBuilder unhandledExceptionBuilder; private final ExceptionPolicy exceptionPolicy; private CheckerRunner(ClassPath classpath, UnhandledExceptionBuilder unhandledExceptionBuilder, ExceptionPolicy exceptionPolicy) { this.classpath = classpath; this.unhandledExceptionBuilder = unhandledExceptionBuilder; this.exceptionPolicy = exceptionPolicy; } public static CheckerRunner createWithClasspath(ClassPath classpath, ExceptionPolicy exceptionPolicy) { return new CheckerRunner(classpath, new UnhandledExceptionBuilder(), exceptionPolicy); } public static CheckerRunner createWithCurrentClasspath(ExceptionPolicy exceptionPolicy) { return createWithClasspath(new ClassPathFactory().createFromJVM(), exceptionPolicy); } public enum ExceptionPolicy { FAIL_FAST, CARRY_ON } public CheckerResult run(AsmMutabilityChecker checker, Dotted className, Iterable<AnalysisResult> resultsSoFar) { Optional<AnalysisError> potentialError = runVisitor(checker, className, resultsSoFar); if (potentialError.isPresent()) { return new CheckerResult( CANNOT_ANALYSE.createsResult(), singleton(newMutableReasonDetail("Encountered an unhandled error in analysis.", codeLocationOf(className), CANNOT_ANALYSE)), singleton(potentialError.get())); } else { return checker.checkerResult(); } } public Optional<AnalysisError> runVisitor(AsmClassVisitor visitor, Dotted className, Iterable<AnalysisResult> resultsSoFar) { try { try { analyseFromStream(visitor, className); } catch (Exception e) { analyseFromClassLoader(visitor, className); } } catch (Throwable e) { return Optional.of(attemptRecovery(visitor, className, resultsSoFar, e)); } return Optional.absent(); } private CodeLocation<?> codeLocationOf(Dotted className) { return className != null ? CodeLocation.ClassLocation.from(className) : CodeLocation.UnknownCodeLocation.UNKNOWN; } private void analyseFromStream(ClassVisitor checker, Dotted dottedClassPath) throws IOException { InputStream classStream = classpath.getResourceAsStream(dottedClassPath.asResource()); analyse(checker, classStream); } private void analyseFromClassLoader(ClassVisitor checker, Dotted className) throws Exception { InputStream classStream = getClass().getClassLoader().getResourceAsStream(className.asResource()); analyse(checker, classStream); } private void analyse(ClassVisitor checker, InputStream classStream) throws IOException { ClassReader cr = new ClassReader(classStream); cr.accept(checker, 0); } private AnalysisError attemptRecovery(ClassVisitor visitor, Dotted className, Iterable<AnalysisResult> resultsSoFar, Throwable error) { if (!isRecoverable(error) || exceptionPolicy == FAIL_FAST) { throw unhandledExceptionBuilder.unhandledException(error, resultsSoFar, visitor, className); } else { return handleException(visitor, className); } } private boolean isRecoverable(Throwable e) { Throwable cause = underlyingCause(e); return (cause instanceof Exception) || (cause instanceof LinkageError); } private Throwable underlyingCause(Throwable e) { Throwable rootCause = e; while (rootCause.getCause() != null) { rootCause = rootCause.getCause(); } return rootCause; } private AnalysisError handleException(ClassVisitor checker, Dotted onClass) { String errorDescription = createErrorDescription(onClass); return new AnalysisError(onClass, getNameOfChecker(checker), errorDescription); } public String createErrorDescription(Dotted dottedClass) { return format("It is likely that the class %s has dependencies outwith the given class path.", dottedClass.asString()); } private String getNameOfChecker(ClassVisitor visitor) { return visitor.getClass().getSimpleName(); } }