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

Gauges should be registered with registerWithReplacement #1123

Merged
merged 6 commits into from
Dec 17, 2019
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ Safe Logging can be found at [github.com/palantir/safe-logging](https://github.c
- `AssertjPrimitiveComparison`: Prefer using AssertJ fluent comparisons over logic in an assertThat statement.
- `ExceptionSpecificity`: Prefer more specific catch types than Exception and Throwable.
- `ThrowSpecificity`: Prefer to declare more specific `throws` types than Exception and Throwable.
- `UnsafeGaugeRegistration`: Use TaggedMetricRegistry.registerWithReplacement over TaggedMetricRegistry.gauge.

### Programmatic Application

Expand Down
1 change: 1 addition & 0 deletions baseline-error-prone/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dependencies {
testCompile 'commons-lang:commons-lang'
testCompile 'org.assertj:assertj-core'
testCompile 'org.jooq:jooq'
testCompile 'com.palantir.tritium:tritium-registry'
testImplementation 'org.junit.jupiter:junit-jupiter'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-migrationsupport'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.baseline.errorprone;

import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.AbstractReturnValueIgnored;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.fixes.SuggestedFixes;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.matchers.Matcher;
import com.google.errorprone.matchers.method.MethodMatchers;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol;

@AutoService(BugChecker.class)
@BugPattern(
name = "UnsafeGaugeRegistration",
link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks",
linkType = BugPattern.LinkType.CUSTOM,
providesFix = BugPattern.ProvidesFix.REQUIRES_HUMAN_ATTENTION,
severity = BugPattern.SeverityLevel.WARNING,
summary = "Using TaggedMetricRegistry.gauge is equivalent to map.putIfAbsent, and can result in subtle "
+ "resource leaks. Prefer replacing existing gauge values.\n"
// This check may begin to fail after a version upgrade, where fixes aren't automatically applied
+ "This can be fixed automatically using "
+ "./gradlew compileJava compileTestJava -PerrorProneApply=UnsafeGaugeRegistration")
public final class UnsafeGaugeRegistration extends AbstractReturnValueIgnored {

private static final String TAGGED_REGISTRY = "com.palantir.tritium.metrics.registry.TaggedMetricRegistry";
private static final String REGISTER_WITH_REPLACEMENT = "registerWithReplacement";
private static final Matcher<ExpressionTree> MATCHER = MethodMatchers.instanceMethod()
.onDescendantOf(TAGGED_REGISTRY)
.named("gauge")
.withParameters("com.palantir.tritium.metrics.registry.MetricName", "com.codahale.metrics.Gauge");

@Override
public Matcher<? super ExpressionTree> specializedMatcher() {
return MATCHER;
}

// Override matchMethodInvocation from AbstractReturnValueIgnored to apply our suggested fix.
@Override
public Description matchMethodInvocation(MethodInvocationTree methodInvocationTree, VisitorState state) {
Description description = super.matchMethodInvocation(methodInvocationTree, state);
if (Description.NO_MATCH.equals(description)
|| !hasRegisterWithReplacement(state)
|| TestCheckUtils.isTestCode(state)) {
return Description.NO_MATCH;
}
return buildDescription(methodInvocationTree)
.addFix(SuggestedFixes.renameMethodInvocation(methodInvocationTree, REGISTER_WITH_REPLACEMENT, state))
.build();
}

/**
* TaggedMetricRegistry.registerWithReplacement was added in Tritium 0.16.1, avoid flagging older versions.
*/
private static boolean hasRegisterWithReplacement(VisitorState state) {
Symbol symbol = state.getSymbolFromString(TAGGED_REGISTRY);
if (!(symbol instanceof Symbol.ClassSymbol)) {
return false;
}
Symbol.ClassSymbol classSymbol = (Symbol.ClassSymbol) symbol;
for (Symbol enclosed : classSymbol.getEnclosedElements()) {
if (enclosed instanceof Symbol.MethodSymbol) {
Symbol.MethodSymbol enclosedMethod = (Symbol.MethodSymbol) enclosed;
if (enclosedMethod.name.contentEquals(REGISTER_WITH_REPLACEMENT)) {
return true;
}
}
}
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.baseline.errorprone;

import com.google.errorprone.BugCheckerRefactoringTestHelper;
import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;

class UnsafeGaugeRegistrationTest {

@Test
void testFix() {
RefactoringValidator.of(new UnsafeGaugeRegistration(), getClass())
.addInputLines(
"Test.java",
"import com.palantir.tritium.metrics.registry.MetricName;",
"import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;",
"class Test {",
" void f(TaggedMetricRegistry registry, MetricName name) {",
" registry.gauge(name, () -> 1);",
" }",
"}")
.addOutputLines(
"Test.java",
"import com.palantir.tritium.metrics.registry.MetricName;",
"import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;",
"class Test {",
" void f(TaggedMetricRegistry registry, MetricName name) {",
" registry.registerWithReplacement(name, () -> 1);",
" }",
"}"
)
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
}

@Test
void testKnownBug() {
RefactoringValidator.of(new UnsafeGaugeRegistration(), getClass())
.addInputLines(
"Test.java",
"import com.codahale.metrics.Gauge;",
"import com.palantir.tritium.metrics.registry.MetricName;",
"import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;",
"class Test {",
" void f(TaggedMetricRegistry registry, MetricName name, Gauge<?> gauge) {",
" registry.gauge(name, gauge);",
" }",
"}")
.addOutputLines(
"Test.java",
"import com.codahale.metrics.Gauge;",
"import com.palantir.tritium.metrics.registry.MetricName;",
"import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;",
"class Test {",
" void f(TaggedMetricRegistry registry, MetricName name, Gauge<?> gauge) {",
// This isn't right. Filed https://github.com/google/error-prone/issues/1451
" registry.gauge(name, registerWithReplacement);",
" }",
"}"
)
.doTest(BugCheckerRefactoringTestHelper.TestMode.TEXT_MATCH);
}

@Test
void testNegative() {
CompilationTestHelper.newInstance(UnsafeGaugeRegistration.class, getClass()).addSourceLines(
"Test.java",
"import com.codahale.metrics.Gauge;",
"import com.palantir.tritium.metrics.registry.MetricName;",
"import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;",
"class Test {",
" Gauge<?> f(TaggedMetricRegistry registry, MetricName name) {",
" return registry.gauge(name, () -> 1);",
" }",
"}").doTest();
}
}
4 changes: 4 additions & 0 deletions baseline-refaster-rules/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@ dependencies {
implementation 'com.google.errorprone:error_prone_refaster'
implementation 'org.assertj:assertj-core'
implementation 'org.mockito:mockito-core'
implementation 'com.palantir.tritium:tritium-registry'

testCompile 'junit:junit'
testCompile project(':baseline-refaster-testing')
testCompile 'org.immutables:value::annotations'

compileOnly 'org.immutables:value::annotations'
}

// Do not apply refaster rules
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.baseline.refaster;

import com.codahale.metrics.Gauge;
import com.google.errorprone.refaster.annotation.AfterTemplate;
import com.google.errorprone.refaster.annotation.BeforeTemplate;
import com.palantir.tritium.metrics.registry.MetricName;
import com.palantir.tritium.metrics.registry.TaggedMetricRegistry;

/**
* Remove unnecessary 'remove' invocation prior to a 'registerWithReplacement'.
* This refaster rule pairs with the 'UnsafeGaugeRegistration' error-prone rule
* to replace 'registry.remove(name); registry.gauge(name, value);' with a
* single 'registerWithReplacement' call.
* This refaster rule intentionally doesn't check the 'gauge' method in order to
* avoid creating changes that don't compile when older versions of Tritium are
* present.
*/
public final class TritiumReplaceGauge<T> {

@BeforeTemplate
void before(TaggedMetricRegistry registry, MetricName name, Gauge<T> gauge) {
registry.remove(name);
registry.registerWithReplacement(name, gauge);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a template to replace following pattern with registry.registerWithReplacement(name, gauge);?

    @BeforeTemplate
    void before(TaggedMetricRegistry registry, MetricName name, Gauge<T> gauge) {
        registry.remove(name);
        registry.gauge(name, gauge);
    }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to create a change that doesn't compile on old versions of Tritium (pre 0.16.1). The error-prone check only runs on new tritium, and transforms registry.gauge(name, gauge) into registry.registerWithReplacement(name, gauge), allowing this rule to do the rest.
Documented in the class level javadoc :-)

}

@AfterTemplate
void after(TaggedMetricRegistry registry, MetricName name, Gauge<T> gauge) {
registry.registerWithReplacement(name, gauge);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* (c) Copyright 2019 Palantir Technologies Inc. All rights reserved.
*
* 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.palantir.baseline.refaster;

import org.junit.Test;

public class TritiumReplaceGaugeTest {

@Test
public void test() {
RefasterTestHelper
.forRefactoring(TritiumReplaceGauge.class)
.withInputLines(
"Test",
"import com.codahale.metrics.Gauge;",
"import com.palantir.tritium.metrics.registry.DefaultTaggedMetricRegistry;",
"import com.palantir.tritium.metrics.registry.MetricName;",
"public class Test {",
" void f(DefaultTaggedMetricRegistry registry) {",
" MetricName name = MetricName.builder().safeName(\"foo\").build();",
" registry.remove(name);",
" registry.registerWithReplacement(name, () -> 1);",
" }",
"}")
.hasOutputLines(
"import com.codahale.metrics.Gauge;",
"import com.palantir.tritium.metrics.registry.DefaultTaggedMetricRegistry;",
"import com.palantir.tritium.metrics.registry.MetricName;",
"public class Test {",
" void f(DefaultTaggedMetricRegistry registry) {",
" MetricName name = MetricName.builder().safeName(\"foo\").build();",
" registry.registerWithReplacement(name, () -> 1);",
" ",
" }",
"}");
}

}
11 changes: 11 additions & 0 deletions changelog/@unreleased/pr-1123.v2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
type: improvement
improvement:
description: |-
Using TaggedMetricRegistry.gauge is equivalent to map.putIfAbsent, and
can result in subtle resource leaks. Prefer replacing existing gauge
values using `registerWithReplacement`.

This check doesn't apply unless a new enough version of Tritium
is available on the compilation classpath.
links:
- https://github.com/palantir/gradle-baseline/pull/1123
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public class BaselineErrorProneExtension {
"ThrowError",
// TODO(ckozak): re-enable pending scala check
//"ThrowSpecificity",
"UnsafeGaugeRegistration",

// Built-in checks
"ArrayEquals",
Expand Down
Loading