Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate annotated variable and field assignment safety #2160

Merged
merged 2 commits into from
Apr 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import com.palantir.baseline.errorprone.safety.Safety;
import com.palantir.baseline.errorprone.safety.SafetyAnalysis;
import com.palantir.baseline.errorprone.safety.SafetyAnnotations;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
Expand Down Expand Up @@ -56,7 +58,10 @@
severity = BugPattern.SeverityLevel.ERROR,
summary = "safe-logging annotations must agree between args and method parameters")
public final class IllegalSafeLoggingArgument extends BugChecker
implements BugChecker.MethodInvocationTreeMatcher, BugChecker.ReturnTreeMatcher {
implements BugChecker.MethodInvocationTreeMatcher,
BugChecker.ReturnTreeMatcher,
BugChecker.AssignmentTreeMatcher,
BugChecker.CompoundAssignmentTreeMatcher {

private static final String UNSAFE_ARG = "com.palantir.logsafe.UnsafeArg";
private static final Matcher<ExpressionTree> SAFE_ARG_OF_METHOD_MATCHER = MethodMatchers.staticMethod()
Expand Down Expand Up @@ -102,6 +107,17 @@ public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState
return Description.NO_MATCH;
}

private static SuggestedFix getSuggestedFix(MethodInvocationTree tree, VisitorState state, Safety argumentSafety) {
if (SAFE_ARG_OF_METHOD_MATCHER.matches(tree, state) && Safety.UNSAFE.allowsValueWith(argumentSafety)) {
SuggestedFix.Builder fix = SuggestedFix.builder();
String unsafeQualifiedClassName = SuggestedFixes.qualifyType(state, fix, UNSAFE_ARG);
String replacement = String.format("%s.of", unsafeQualifiedClassName);
return fix.replace(tree.getMethodSelect(), replacement).build();
}

return SuggestedFix.emptyFix();
}

@Override
public Description matchReturn(ReturnTree tree, VisitorState state) {
if (tree.getExpression() == null) {
Expand Down Expand Up @@ -132,14 +148,30 @@ public Description matchReturn(ReturnTree tree, VisitorState state) {
.build();
}

private static SuggestedFix getSuggestedFix(MethodInvocationTree tree, VisitorState state, Safety argumentSafety) {
if (SAFE_ARG_OF_METHOD_MATCHER.matches(tree, state) && Safety.UNSAFE.allowsValueWith(argumentSafety)) {
SuggestedFix.Builder fix = SuggestedFix.builder();
String unsafeQualifiedClassName = SuggestedFixes.qualifyType(state, fix, UNSAFE_ARG);
String replacement = String.format("%s.of", unsafeQualifiedClassName);
return fix.replace(tree.getMethodSelect(), replacement).build();
}
@Override
public Description matchAssignment(AssignmentTree tree, VisitorState state) {
return handleAssignment(tree, tree.getVariable(), tree.getExpression(), state);
}

return SuggestedFix.emptyFix();
@Override
public Description matchCompoundAssignment(CompoundAssignmentTree tree, VisitorState state) {
return handleAssignment(tree, tree.getVariable(), tree.getExpression(), state);
}

private Description handleAssignment(
ExpressionTree assignmentTree, ExpressionTree variable, ExpressionTree expression, VisitorState state) {
Safety variableDeclaredSafety = SafetyAnnotations.getSafety(ASTHelpers.getSymbol(variable), state);
if (variableDeclaredSafety.allowsAll()) {
return Description.NO_MATCH;
}
Safety assignmentValue = SafetyAnalysis.of(state.withPath(new TreePath(state.getPath(), expression)));
if (variableDeclaredSafety.allowsValueWith(assignmentValue)) {
return Description.NO_MATCH;
}
return buildDescription(assignmentTree)
.setMessage(String.format(
"Dangerous assignment: value is '%s' but the variable is annotated '%s'.",
assignmentValue, variableDeclaredSafety))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -588,14 +588,18 @@ public TransferResult<Safety, AccessPathStore<Safety>> visitSwitchExpressionNode
public TransferResult<Safety, AccessPathStore<Safety>> visitAssignment(
AssignmentNode node, TransferInput<Safety, AccessPathStore<Safety>> input) {
ReadableUpdates updates = new ReadableUpdates();
Safety safety = input.getValueOfSubNode(node.getExpression());
Safety expressionSafety = input.getValueOfSubNode(node.getExpression());
Safety targetSymbolSafety = SafetyAnnotations.getSafety(
ASTHelpers.getSymbol(node.getTarget().getTree()), state);
Safety safety = Safety.mergeAssumingUnknownIsSame(expressionSafety, targetSymbolSafety);
Node target = node.getTarget();
if (target instanceof LocalVariableNode) {
updates.trySet(target, safety);
} else if (target instanceof ArrayAccessNode) {
Node arrayNode = ((ArrayAccessNode) target).getArray();
Safety arrayCombinedSafety = input.getValueOfSubNode(arrayNode).leastUpperBound(safety);
updates.trySet(arrayNode, arrayCombinedSafety);
Safety arrayNodeSafety = input.getValueOfSubNode(arrayNode);
safety = arrayNodeSafety == null ? safety : arrayNodeSafety.leastUpperBound(safety);
updates.trySet(arrayNode, safety);
} else if (target instanceof FieldAccessNode) {
FieldAccessNode fieldAccess = (FieldAccessNode) target;
updates.set(fieldAccess, safety);
Expand Down Expand Up @@ -626,8 +630,10 @@ private static boolean hasNonNullConstantValue(LocalVariableNode node) {
@Override
public TransferResult<Safety, AccessPathStore<Safety>> visitVariableDeclaration(
VariableDeclarationNode node, TransferInput<Safety, AccessPathStore<Safety>> input) {
Safety safety =
Safety variableTypeSafety =
SafetyAnnotations.getSafety(ASTHelpers.getSymbol(node.getTree().getType()), state);
Safety variableSafety = SafetyAnnotations.getSafety(ASTHelpers.getSymbol(node.getTree()), state);
Safety safety = Safety.mergeAssumingUnknownIsSame(variableTypeSafety, variableSafety);
return noStoreChanges(safety, input);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,79 @@ public void testImmutableCollectionFactories() {
.doTest();
}

@Test
public void testFieldAnnotation() {
helper().addSourceLines(
"Test.java",
"import com.palantir.logsafe.*;",
"class Test {",
" @Unsafe private static final String SECRET = System.getProperty(\"foo\");",
" void f() {",
" // BUG: Diagnostic contains: Dangerous argument value: arg is 'UNSAFE' "
+ "but the parameter requires 'SAFE'.",
" fun(SECRET);",
" }",
" private static void fun(@Safe Object obj) {}",
"}")
.doTest();
}

@Test
public void testLocalAnnotation() {
helper().addSourceLines(
"Test.java",
"import com.palantir.logsafe.*;",
"class Test {",
" void f() {",
" @DoNotLog String localVar = System.getProperty(\"bar\");",
" // BUG: Diagnostic contains: Dangerous argument value: arg is 'DO_NOT_LOG' "
+ "but the parameter requires 'SAFE'.",
" fun(localVar);",
" }",
" private static void fun(@Safe Object obj) {}",
"}")
.doTest();
}

@Test
public void testLocalAnnotationAssignment() {
helper().addSourceLines(
"Test.java",
"import com.palantir.logsafe.*;",
"class Test {",
" void f(@Safe String safeParam, @Unsafe String unsafeParam, @DoNotLog String dnlParam) {",
" @Safe String local = safeParam;",
" // BUG: Diagnostic contains: Dangerous assignment: value is 'UNSAFE' "
+ "but the variable is annotated 'SAFE'.",
" local = unsafeParam;",
" // BUG: Diagnostic contains: Dangerous assignment: value is 'DO_NOT_LOG' "
+ "but the variable is annotated 'SAFE'.",
" local = dnlParam;",
" }",
"}")
.doTest();
}

@Test
public void testFieldAnnotationAssignment() {
helper().addSourceLines(
"Test.java",
"import com.palantir.logsafe.*;",
"class Test {",
" @Safe private static String field;",
" void f(@Safe String safeParam, @Unsafe String unsafeParam, @DoNotLog String dnlParam) {",
" field = safeParam;",
" // BUG: Diagnostic contains: Dangerous assignment: value is 'UNSAFE' "
+ "but the variable is annotated 'SAFE'.",
" field = unsafeParam;",
" // BUG: Diagnostic contains: Dangerous assignment: value is 'DO_NOT_LOG' "
+ "but the variable is annotated 'SAFE'.",
" field = dnlParam;",
" }",
"}")
.doTest();
}

@Test
public void testSafeArgOfUnsafe_recommendsUnsafeArgOf() {
refactoringHelper()
Expand Down
5 changes: 5 additions & 0 deletions changelog/@unreleased/pr-2160.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
type: improvement
improvement:
description: Validate annotated variable and field assignment safety
links:
- https://github.com/palantir/gradle-baseline/pull/2160