diff --git a/java/java-impl/java-impl.iml b/java/java-impl/java-impl.iml index faff76e9474a..90be2386c2c3 100644 --- a/java/java-impl/java-impl.iml +++ b/java/java-impl/java-impl.iml @@ -26,6 +26,7 @@ + diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/FieldAccessNotGuardedInspection.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/FieldAccessNotGuardedInspection.java new file mode 100644 index 000000000000..9b7400e96b32 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/FieldAccessNotGuardedInspection.java @@ -0,0 +1,93 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class FieldAccessNotGuardedInspection extends BaseJavaLocalInspectionTool { + + @NotNull + public String getGroupDisplayName() { + return GroupNames.CONCURRENCY_ANNOTATION_ISSUES; + } + + @Nls + @NotNull + public String getDisplayName() { + return "Unguarded field access"; + } + + @NotNull + public String getShortName() { + return "FieldAccessNotGuarded"; + } + + @NotNull + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + + private static class Visitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + + public Visitor(ProblemsHolder holder) { + myHolder = holder; + } + + public void visitReferenceExpression(PsiReferenceExpression expression) { + final PsiElement referent = expression.resolve(); + if (referent == null || !(referent instanceof PsiField)) { + return; + } + final PsiField field = (PsiField)referent; + final String guard = JCiPUtil.findGuardForMember(field); + if (guard == null) { + return; + } + final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(expression, PsiMethod.class); + if (containingMethod != null && JCiPUtil.isGuardedBy(containingMethod, guard)) { + return; + } + if ("this".equals(guard)) { + if (containingMethod != null && containingMethod.hasModifierProperty(PsiModifier.SYNCHRONIZED)) { + return; + } + } + PsiElement check = expression; + while (true) { + final PsiSynchronizedStatement syncStatement = PsiTreeUtil.getParentOfType(check, PsiSynchronizedStatement.class); + if (syncStatement == null) { + break; + } + final PsiExpression lockExpression = syncStatement.getLockExpression(); + if (lockExpression != null && lockExpression.getText().equals(guard)) //TODO: this isn't quite right, + { + return; + } + check = syncStatement; + } + //TODO: see if there is a lock via a .lock* call + myHolder.registerProblem(expression, "Access to field #ref outside of declared guards #loc"); + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/InstanceGuardedByStaticInspection.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/InstanceGuardedByStaticInspection.java new file mode 100644 index 000000000000..e2572a16e7e4 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/InstanceGuardedByStaticInspection.java @@ -0,0 +1,124 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import com.intellij.psi.javadoc.PsiDocTag; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class InstanceGuardedByStaticInspection extends BaseJavaLocalInspectionTool { + + @NotNull + public String getGroupDisplayName() { + return GroupNames.CONCURRENCY_ANNOTATION_ISSUES; + } + + @Nls + @NotNull + public String getDisplayName() { + return "Instance member guarded by static field"; + } + + @NotNull + public String getShortName() { + return "InstanceGuardedByStatic"; + } + + @NotNull + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + private static class Visitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + + public Visitor(ProblemsHolder holder) { + myHolder = holder; + } + + + public void visitDocTag(PsiDocTag psiDocTag) { + super.visitDocTag(psiDocTag); + if (!JCiPUtil.isGuardedByTag(psiDocTag)) { + return; + } + final PsiMember member = PsiTreeUtil.getParentOfType(psiDocTag, PsiMember.class); + if (member == null) { + return; + } + if (member.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(psiDocTag); + + final PsiClass containingClass = PsiTreeUtil.getParentOfType(psiDocTag, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField == null) { + return; + } + if (!guardField.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + myHolder.registerProblem(psiDocTag, "Instance member guarded by static \"" + guardValue + "\" #loc"); + } + + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + + public void visitAnnotation(PsiAnnotation annotation) { + super.visitAnnotation(annotation); + if (!JCiPUtil.isGuardedByAnnotation(annotation)) { + return; + } + final PsiMember member = PsiTreeUtil.getParentOfType(annotation, PsiMember.class); + if (member == null) { + return; + } + if (member.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(annotation); + if (guardValue == null) { + return; + } + + final PsiAnnotationMemberValue guardRef = annotation.findAttributeValue("value"); + if (guardRef == null) { + return; + } + final PsiClass containingClass = PsiTreeUtil.getParentOfType(annotation, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField == null) { + return; + } + if (!guardField.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + myHolder.registerProblem(guardRef, "Instance member guarded by static #ref #loc"); + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/JCiPOrderEntryFix.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/JCiPOrderEntryFix.java new file mode 100644 index 000000000000..af22103f32b6 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/JCiPOrderEntryFix.java @@ -0,0 +1,86 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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. + */ + +/* + * User: anna + * Date: 30-Jul-2007 + */ +package com.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.TargetElementUtil; +import com.intellij.codeInsight.daemon.impl.quickfix.OrderEntryFix; +import com.intellij.codeInsight.intention.IntentionAction; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ProjectFileIndex; +import com.intellij.openapi.roots.ProjectRootManager; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.*; +import com.intellij.psi.util.PsiUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.PathUtil; +import net.jcip.annotations.GuardedBy; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; + +public class JCiPOrderEntryFix implements IntentionAction { + private static final Logger LOG = Logger.getInstance("#" + JCiPOrderEntryFix.class.getName()); + + @NotNull + public String getText() { + return "Add jcip-annotations.jar to classpath"; + } + + @NotNull + public String getFamilyName() { + return getText(); + } + + public boolean isAvailable(@NotNull final Project project, final Editor editor, final PsiFile file) { + if (!(file instanceof PsiJavaFile)) return false; + + final PsiReference reference = TargetElementUtil.findReference(editor); + if (!(reference instanceof PsiJavaCodeReferenceElement)) return false; + if (reference.resolve() != null) return false; + @NonNls final String referenceName = ((PsiJavaCodeReferenceElement)reference).getReferenceName(); + if (referenceName == null) return false; + final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + final VirtualFile virtualFile = file.getVirtualFile(); + if (virtualFile == null) return false; + if (fileIndex.getModuleForFile(virtualFile) == null) return false; + if (!(((PsiJavaCodeReferenceElement)reference).getParent() instanceof PsiAnnotation && + PsiUtil.isLanguageLevel5OrHigher(((PsiJavaCodeReferenceElement)reference)))) return false; + if (!JCiPUtil.isJCiPAnnotation(referenceName)) return false; + return true; + } + + public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file) throws IncorrectOperationException { + final PsiJavaCodeReferenceElement reference = (PsiJavaCodeReferenceElement)TargetElementUtil.findReference(editor); + LOG.assertTrue(reference != null); + String jarPath = PathUtil.getJarPathForClass(GuardedBy.class); + final VirtualFile virtualFile = file.getVirtualFile(); + LOG.assertTrue(virtualFile != null); + OrderEntryFix.addBundledJarToRoots(project, editor, ModuleUtil.findModuleForFile(virtualFile, project), reference, + "net.jcip.annotations." + reference.getReferenceName(), jarPath); + } + + public boolean startInWriteAction() { + return true; + } + +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/JCiPUtil.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/JCiPUtil.java new file mode 100644 index 000000000000..755e568d170d --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/JCiPUtil.java @@ -0,0 +1,151 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.AnnotationUtil; +import com.intellij.psi.*; +import com.intellij.psi.javadoc.PsiDocTag; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public class JCiPUtil { + @NonNls + private static final String IMMUTABLE = "net.jcip.annotations.Immutable"; + @NonNls + private static final String GUARDED_BY = "net.jcip.annotations.GuardedBy"; + @NonNls + private static final String THREAD_SAFE = "net.jcip.annotations.ThreadSafe"; + + public static boolean isJCiPAnnotation(String ref) { + return "Immutable".equals(ref) || "GuardedBy".equals(ref) || "ThreadSafe".equals("ref"); + } + + private JCiPUtil() { + } + + public static boolean isImmutable(PsiClass aClass) { + final PsiAnnotation annotation = AnnotationUtil.findAnnotation(aClass, IMMUTABLE); + if (annotation != null) { + return true; + } + final ImmutableTagVisitor visitor = new ImmutableTagVisitor(); + aClass.accept(visitor); + return visitor.isFound(); + } + + @Nullable + public static String findGuardForMember(PsiMember member) { + final PsiAnnotation annotation = AnnotationUtil.findAnnotation(member, GUARDED_BY); + if (annotation != null) { + return getGuardValue(annotation); + } + + final GuardedTagVisitor visitor = new GuardedTagVisitor(); + member.accept(visitor); + return visitor.getGuardString(); + } + + public static boolean isGuardedBy(PsiMember member, String guard) { + + final PsiAnnotation annotation = AnnotationUtil.findAnnotation(member, GUARDED_BY); + if (annotation != null) { + final PsiAnnotationParameterList parameters = annotation.getParameterList(); + final PsiNameValuePair[] pairs = parameters.getAttributes(); + final String fieldName = '"' + guard + '"'; + for (PsiNameValuePair pair : pairs) { + final String name = pair.getName(); + if (("value".equals(name) || name == null)) { + final PsiAnnotationMemberValue value = pair.getValue(); + if (value != null && value.getText().equals(fieldName)) { + return true; + } + } + } + } + return false; + } + + public static boolean isGuardedBy(PsiMember member, PsiField field) { + return isGuardedBy(member, field.getName()); + } + + public static boolean isGuardedByAnnotation(PsiAnnotation annotation) { + return GUARDED_BY.equals(annotation.getQualifiedName()); + } + + public static boolean isGuardedByTag(PsiDocTag tag) { + final String text = tag.getText(); + + return text.startsWith("@GuardedBy") && text.contains("(") && text.contains(")"); + } + + @Nullable + public static String getGuardValue(PsiAnnotation annotation) { + final PsiAnnotationParameterList parameters = annotation.getParameterList(); + final PsiNameValuePair[] pairs = parameters.getAttributes(); + for (PsiNameValuePair pair : pairs) { + final String name = pair.getName(); + if ("value".equals(name) || name == null) { + final PsiAnnotationMemberValue psiAnnotationMemberValue = pair.getValue(); + if (psiAnnotationMemberValue != null) { + final String value = psiAnnotationMemberValue.getText(); + return value.substring(1, value.length() - 1).trim(); + } + } + } + return null; + } + + @NotNull + public static String getGuardValue(PsiDocTag tag) { + final String text = tag.getText(); + return text.substring(text.indexOf((int)'(') + 1, text.indexOf((int)')')).trim(); + } + + private static class GuardedTagVisitor extends JavaRecursiveElementVisitor { + private String guardString = null; + + public void visitDocTag(PsiDocTag tag) { + super.visitDocTag(tag); + final String text = tag.getText(); + if (text.startsWith("@GuardedBy") && text.contains("(") && text.contains(")")) { + guardString = text.substring(text.indexOf((int)'(') + 1, text.indexOf((int)')')); + } + } + + @Nullable + public String getGuardString() { + return guardString; + } + } + + private static class ImmutableTagVisitor extends JavaRecursiveElementVisitor { + private boolean found = false; + + public void visitDocTag(PsiDocTag tag) { + super.visitDocTag(tag); + final String text = tag.getText(); + if (text.startsWith("@Immutable")) { + found = true; + } + } + + public boolean isFound() { + return found; + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/NonFinalFieldInImmutableInspection.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/NonFinalFieldInImmutableInspection.java new file mode 100644 index 000000000000..0757fc0e9f6e --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/NonFinalFieldInImmutableInspection.java @@ -0,0 +1,63 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class NonFinalFieldInImmutableInspection extends BaseJavaLocalInspectionTool { + + @NotNull + public String getGroupDisplayName() { + return GroupNames.CONCURRENCY_ANNOTATION_ISSUES; + } + + @Nls + @NotNull + public String getDisplayName() { + return "Non-final field in @Immutable class"; + } + + @NotNull + public String getShortName() { + return "NonFinalFieldInImmutable"; + } + + + @NotNull + public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) { + return new JavaElementVisitor() { + public void visitField(PsiField field) { + super.visitField(field); + if (field.hasModifierProperty(PsiModifier.FINAL)) { + return; + } + final PsiClass containingClass = field.getContainingClass(); + if (!JCiPUtil.isImmutable(containingClass)) { + return; + } + holder.registerProblem(field, "Non-final field #ref in @Immutable class #loc"); + } + + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + }; + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/NonFinalGuardInspection.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/NonFinalGuardInspection.java new file mode 100644 index 000000000000..44a665860608 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/NonFinalGuardInspection.java @@ -0,0 +1,112 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import com.intellij.psi.javadoc.PsiDocTag; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class NonFinalGuardInspection extends BaseJavaLocalInspectionTool { + + @NotNull + public String getGroupDisplayName() { + return GroupNames.CONCURRENCY_ANNOTATION_ISSUES; + } + + @Nls + @NotNull + public String getDisplayName() { + return "Non-final @GuardedBy field"; + } + + @NotNull + public String getShortName() { + return "NonFinalGuard"; + } + + + @NotNull + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + private static class Visitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + + public Visitor(ProblemsHolder holder) { + + myHolder = holder; + } + + public void visitAnnotation(PsiAnnotation annotation) { + super.visitAnnotation(annotation); + if (!JCiPUtil.isGuardedByAnnotation(annotation)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(annotation); + if (guardValue == null || "this".equals(guardValue)) { + return; + } + final PsiClass containingClass = PsiTreeUtil.getParentOfType(annotation, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField == null) { + return; + } + if (guardField.hasModifierProperty(PsiModifier.FINAL)) { + return; + } + final PsiAnnotationMemberValue member = annotation.findAttributeValue("value"); + if (member == null) { + return; + } + myHolder.registerProblem(member, "Non-final @GuardedBy field #ref #loc"); + } + + public void visitDocTag(PsiDocTag psiDocTag) { + super.visitDocTag(psiDocTag); + if (!JCiPUtil.isGuardedByTag(psiDocTag)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(psiDocTag); + if ("this".equals(guardValue)) { + return; + } + final PsiClass containingClass = PsiTreeUtil.getParentOfType(psiDocTag, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField == null) { + return; + } + if (guardField.hasModifierProperty(PsiModifier.FINAL)) { + return; + } + myHolder.registerProblem(psiDocTag, "Non-final @GuardedBy field \"" + guardValue + "\" #loc"); + } + + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/StaticGuardedByInstanceInspection.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/StaticGuardedByInstanceInspection.java new file mode 100644 index 000000000000..41623606c6ee --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/StaticGuardedByInstanceInspection.java @@ -0,0 +1,133 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import com.intellij.psi.javadoc.PsiDocTag; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class StaticGuardedByInstanceInspection extends BaseJavaLocalInspectionTool { + + @NotNull + public String getGroupDisplayName() { + return GroupNames.CONCURRENCY_ANNOTATION_ISSUES; + } + + @Nls + @NotNull + public String getDisplayName() { + return "Static member guarded by instance field or this"; + } + + @NotNull + public String getShortName() { + return "StaticGuardedByInstance"; + } + + @NotNull + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + private static class Visitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + + public Visitor(ProblemsHolder holder) { + myHolder = holder; + } + + public void visitAnnotation(PsiAnnotation annotation) { + super.visitAnnotation(annotation); + if (!JCiPUtil.isGuardedByAnnotation(annotation)) { + return; + } + final PsiMember member = PsiTreeUtil.getParentOfType(annotation, PsiMember.class); + if (member == null) { + return; + } + if (!member.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(annotation); + if (guardValue == null) { + return; + } + + final PsiAnnotationMemberValue guardRef = annotation.findAttributeValue("value"); + if (guardRef == null) { + return; + } + if ("this".equals(guardValue)) { + registerError(guardRef); + } + final PsiClass containingClass = PsiTreeUtil.getParentOfType(annotation, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField == null) { + return; + } + if (guardField.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + registerError(guardRef); + } + + public void visitDocTag(PsiDocTag psiDocTag) { + super.visitDocTag(psiDocTag); + if (!JCiPUtil.isGuardedByTag(psiDocTag)) { + return; + } + final PsiMember member = PsiTreeUtil.getParentOfType(psiDocTag, PsiMember.class); + if (member == null) { + return; + } + if (!member.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(psiDocTag); + + if ("this".equals(guardValue)) { + registerError(psiDocTag); + } + final PsiClass containingClass = PsiTreeUtil.getParentOfType(psiDocTag, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField == null) { + return; + } + if (guardField.hasModifierProperty(PsiModifier.STATIC)) { + return; + } + myHolder.registerProblem(psiDocTag, "Static member guarded by instance \"" + guardValue + "\" #loc"); + } + + private void registerError(PsiElement element) { + myHolder.registerProblem(element, "Static member guarded by instance #ref #loc"); + } + + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + } +} \ No newline at end of file diff --git a/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/UnknownGuardInspection.java b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/UnknownGuardInspection.java new file mode 100644 index 000000000000..8940bd6c98e0 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/concurrencyAnnotations/UnknownGuardInspection.java @@ -0,0 +1,104 @@ +/* + * Copyright 2000-2009 JetBrains s.r.o. + * + * 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.intellij.codeInspection.concurrencyAnnotations; + +import com.intellij.codeInsight.daemon.GroupNames; +import com.intellij.codeInspection.BaseJavaLocalInspectionTool; +import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.psi.*; +import com.intellij.psi.javadoc.PsiDocTag; +import com.intellij.psi.util.PsiTreeUtil; +import org.jetbrains.annotations.Nls; +import org.jetbrains.annotations.NotNull; + +public class UnknownGuardInspection extends BaseJavaLocalInspectionTool { + + @NotNull + public String getGroupDisplayName() { + return GroupNames.CONCURRENCY_ANNOTATION_ISSUES; + } + + @Nls + @NotNull + public String getDisplayName() { + return "Unknown @GuardedBy field"; + } + + @NotNull + public String getShortName() { + return "UnknownGuard"; + } + + @NotNull + public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) { + return new Visitor(holder); + } + + private static class Visitor extends JavaElementVisitor { + private final ProblemsHolder myHolder; + + public Visitor(ProblemsHolder holder) { + myHolder = holder; + } + + public void visitAnnotation(PsiAnnotation annotation) { + super.visitAnnotation(annotation); + if (!JCiPUtil.isGuardedByAnnotation(annotation)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(annotation); + if (guardValue == null || "this".equals(guardValue)) { + return; + } + final PsiClass containingClass = PsiTreeUtil.getParentOfType(annotation, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField != null) { + return; + } + final PsiAnnotationMemberValue member = annotation.findAttributeValue("value"); + if (member == null) { + return; + } + myHolder.registerProblem(member, "Unknown @GuardedBy field #ref #loc"); + } + + public void visitDocTag(PsiDocTag psiDocTag) { + super.visitDocTag(psiDocTag); + if (!JCiPUtil.isGuardedByTag(psiDocTag)) { + return; + } + final String guardValue = JCiPUtil.getGuardValue(psiDocTag); + if ("this".equals(guardValue)) { + return; + } + final PsiClass containingClass = PsiTreeUtil.getParentOfType(psiDocTag, PsiClass.class); + if (containingClass == null) { + return; + } + final PsiField guardField = containingClass.findFieldByName(guardValue, true); + if (guardField != null) { + return; + } + myHolder.registerProblem(psiDocTag, "Unknown @GuardedBy field \"" + guardValue + "\" #loc"); + } + + public void visitReferenceExpression(PsiReferenceExpression expression) { + } + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/ex/StandardInspectionToolsProvider.java b/java/java-impl/src/com/intellij/codeInspection/ex/StandardInspectionToolsProvider.java index 033ff9d0d69e..5ced11509a14 100644 --- a/java/java-impl/src/com/intellij/codeInspection/ex/StandardInspectionToolsProvider.java +++ b/java/java-impl/src/com/intellij/codeInspection/ex/StandardInspectionToolsProvider.java @@ -20,6 +20,7 @@ import com.intellij.codeInspection.LossyEncodingInspection; import com.intellij.codeInspection.RedundantSuppressInspection; import com.intellij.codeInspection.accessStaticViaInstance.AccessStaticViaInstance; import com.intellij.codeInspection.canBeFinal.CanBeFinalInspection; +import com.intellij.codeInspection.concurrencyAnnotations.*; import com.intellij.codeInspection.dataFlow.DataFlowInspection; import com.intellij.codeInspection.deadCode.DeadCodeInspection; import com.intellij.codeInspection.defUse.DefUseInspection; @@ -108,7 +109,14 @@ public class StandardInspectionToolsProvider implements InspectionToolProvider { UncheckedWarningLocalInspection.class, SuspiciousNameCombinationInspection.class, DuplicateThrowsInspection.class, - LossyEncodingInspection.class + LossyEncodingInspection.class, + + FieldAccessNotGuardedInspection.class, + InstanceGuardedByStaticInspection.class, + NonFinalFieldInImmutableInspection.class, + NonFinalGuardInspection.class, + StaticGuardedByInstanceInspection.class, + UnknownGuardInspection.class }; } } diff --git a/java/openapi/src/com/intellij/codeInsight/daemon/GroupNames.java b/java/openapi/src/com/intellij/codeInsight/daemon/GroupNames.java index c9571cf0d8f0..48c21641e9aa 100644 --- a/java/openapi/src/com/intellij/codeInsight/daemon/GroupNames.java +++ b/java/openapi/src/com/intellij/codeInsight/daemon/GroupNames.java @@ -66,4 +66,5 @@ public interface GroupNames { String MODULARIZATION_GROUP_NAME = InspectionsBundle.message("group.names.modularization.issues"); String JAVAEE_GROUP_NAME = InspectionsBundle.message("group.names.javaee.issues"); + String CONCURRENCY_ANNOTATION_ISSUES = "Concurrency annotation issues"; } diff --git a/lib/jcip-annotations.jar b/lib/jcip-annotations.jar new file mode 100644 index 000000000000..06e9066b880d Binary files /dev/null and b/lib/jcip-annotations.jar differ diff --git a/resources-en/src/inspectionDescriptions/FieldAccessNotGuarded.html b/resources-en/src/inspectionDescriptions/FieldAccessNotGuarded.html new file mode 100644 index 000000000000..3d7ac75b45a6 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/FieldAccessNotGuarded.html @@ -0,0 +1,16 @@ + + + + + + + + + +
+ + This inspection reports any accesses of fields declared as @net.jcip.annotations.GuardedBy + which are are not guarded by an appropriate synchronization structure. +
New in 9
+ + \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/InstanceGuardedByStatic.html b/resources-en/src/inspectionDescriptions/InstanceGuardedByStatic.html new file mode 100644 index 000000000000..a9432b7f6abd --- /dev/null +++ b/resources-en/src/inspectionDescriptions/InstanceGuardedByStatic.html @@ -0,0 +1,20 @@ + + + + + + + + + +
+ + This inspection reports any @net.jcip.annotations.GuardedBy annotations on instance fields or methods, + where + the guard is a static field. Guarding a non-static by a static may result in excessive lock contention, + as access to each locked field in any object instance will prevent simultaneous access to that field in + every object + instance. +
New in 9
+ + \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/NonFinalFieldInImmutable.html b/resources-en/src/inspectionDescriptions/NonFinalFieldInImmutable.html new file mode 100644 index 000000000000..7092fff89125 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/NonFinalFieldInImmutable.html @@ -0,0 +1,17 @@ + + + + + + + + + +
+ + This inspection reports any non-final field in a class with annotation + @net.jcip.annotations.Immutable. This violates the contract of the @Immutable + annotation. +
New in 9
+ + \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/NonFinalGuard.html b/resources-en/src/inspectionDescriptions/NonFinalGuard.html new file mode 100644 index 000000000000..abd783da83da --- /dev/null +++ b/resources-en/src/inspectionDescriptions/NonFinalGuard.html @@ -0,0 +1,17 @@ + + + + + + + + + +
+ + This inspection reports any @net.jcip.annotations.GuardedBy annotations where the guarding field + is not final. Gaurding on a non-final field may result in unexpected race conditions, as locks will + be held on the value of the field (which may change), rather than the field itself. +
New in 9
+ + \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/StaticGuardedByInstance.html b/resources-en/src/inspectionDescriptions/StaticGuardedByInstance.html new file mode 100644 index 000000000000..055b31d6e341 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/StaticGuardedByInstance.html @@ -0,0 +1,21 @@ + + + + + + + + + +
+ + This inspection reports any @net.jcip.annotations.GuardedBy annotations on static fields or methods, + where + the guard is either a non-static field or 'this'. Guarding a static by a non-static may result in + excessive + concurrency, multiple threads may be able to access the guarded field simultaneously, by locking in + different + object contexts. +
New in 9
+ + \ No newline at end of file diff --git a/resources-en/src/inspectionDescriptions/UnknownGuard.html b/resources-en/src/inspectionDescriptions/UnknownGuard.html new file mode 100644 index 000000000000..65ebb3bdfde3 --- /dev/null +++ b/resources-en/src/inspectionDescriptions/UnknownGuard.html @@ -0,0 +1,16 @@ + + + + + + + + + +
+ + This inspection reports any @net.jcip.annotations.GuardedBy annotations where the guarding field + is unknown. +
New in 9
+ + \ No newline at end of file diff --git a/resources/src/META-INF/IdeaPlugin.xml b/resources/src/META-INF/IdeaPlugin.xml index 3d3fffcaea6a..4101d1fb3478 100644 --- a/resources/src/META-INF/IdeaPlugin.xml +++ b/resources/src/META-INF/IdeaPlugin.xml @@ -557,6 +557,10 @@ com.intellij.testIntegration.createTest.CreateTestAction + + com.intellij.codeInspection.concurrencyAnnotations.JCiPOrderEntryFix + +