Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 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 @@ -209,6 +209,12 @@ private void setupErrorProneOptions(CommonOptions commonOptions, ErrorProneOptio
.getCheckOptions()
.putAll(getProviderFactory().provider(commonOptions::extraErrorProneCheckOptions));

errorProneOptions
.getChecks()
.put("RemoveUnusedSuppressions", getProviderFactory().provider(() -> commonOptions
.removeUnusedCheck()
.toCheckSeverity()));

// We disable this to avoid having `Note: [RemoveRolloutSuppressions]` in
// unrelated error messages as it's a suggestion level check. If the remove rollout mode is enabled,
// this check will be explicitly patched, which will enable it by default.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
import com.palantir.gradle.suppressibleerrorprone.modes.common.ModifyCheckApiOption;
import com.palantir.gradle.suppressibleerrorprone.modes.common.ModifyCheckApiOption.CombinedValue;
import com.palantir.gradle.suppressibleerrorprone.modes.interferences.DisableModeInterference;
import com.palantir.gradle.suppressibleerrorprone.modes.interferences.RemoveUnusedModeInterference;
import com.palantir.gradle.suppressibleerrorprone.modes.interferences.RemovingAndSuppressingInterference;
import com.palantir.gradle.suppressibleerrorprone.modes.interferences.SuppressingAndApplyingInterference;
import com.palantir.gradle.suppressibleerrorprone.modes.modes.ApplyMode;
import com.palantir.gradle.suppressibleerrorprone.modes.modes.DisableMode;
import com.palantir.gradle.suppressibleerrorprone.modes.modes.RemoveRolloutMode;
import com.palantir.gradle.suppressibleerrorprone.modes.modes.RemoveUnusedMode;
import com.palantir.gradle.suppressibleerrorprone.modes.modes.SuppressMode;
import com.palantir.gradle.suppressibleerrorprone.modes.modes.TimingsMode;
import java.util.List;
Expand Down Expand Up @@ -60,11 +62,13 @@ public abstract class Modes {
ModeName.APPLY, new ApplyMode(),
ModeName.DISABLE, new DisableMode(),
ModeName.REMOVE_ROLLOUT, new RemoveRolloutMode(),
ModeName.REMOVE_UNUSED, new RemoveUnusedMode(),
ModeName.SUPPRESS, new SuppressMode(),
ModeName.TIMINGS, new TimingsMode());

private final Set<ModeInterference> interferences = Set.of(
new DisableModeInterference(),
new RemoveUnusedModeInterference(),
new RemovingAndSuppressingInterference(),
new SuppressingAndApplyingInterference());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ default RemoveRolloutCheck removeRolloutCheck() {
return RemoveRolloutCheck.DISABLE;
}

default RemoveUnusedCheck removeUnusedCheck() {
return RemoveUnusedCheck.DISABLE;
}

default CommonOptions naivelyCombinedWith(CommonOptions other) {
return new CommonOptions() {
@Override
Expand All @@ -59,6 +63,11 @@ public Map<String, String> extraErrorProneCheckOptions() {
public RemoveRolloutCheck removeRolloutCheck() {
return CommonOptions.this.removeRolloutCheck().or(other.removeRolloutCheck());
}

@Override
public RemoveUnusedCheck removeUnusedCheck() {
return CommonOptions.this.removeUnusedCheck().or(other.removeUnusedCheck());
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public enum ModeName {
APPLY("errorProneApply"),
SUPPRESS("errorProneSuppress"),
REMOVE_ROLLOUT("errorProneRemoveRollout"),
REMOVE_UNUSED("errorProneRemoveUnused"),
TIMINGS("errorProneTimings"),
// Historically, the logic of this plugin lived in baseline, so we need to support the old disable flag.
DISABLE("errorProneDisable", "com.palantir.baseline-error-prone.disable"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* (c) Copyright 2025 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.gradle.suppressibleerrorprone.modes.common;

import net.ltgt.gradle.errorprone.CheckSeverity;

public enum RemoveUnusedCheck {
DISABLE,
ENABLED;

public CheckSeverity toCheckSeverity() {
return switch (this) {
case DISABLE -> CheckSeverity.OFF;
case ENABLED -> CheckSeverity.DEFAULT;
};
}

public RemoveUnusedCheck or(RemoveUnusedCheck other) {
return switch (this) {
case DISABLE -> other;
case ENABLED -> this;
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* (c) Copyright 2025 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.gradle.suppressibleerrorprone.modes.interferences;

import com.palantir.gradle.suppressibleerrorprone.modes.common.ModeInterference;
import com.palantir.gradle.suppressibleerrorprone.modes.common.ModeInterferenceResult;
import com.palantir.gradle.suppressibleerrorprone.modes.common.ModeName;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
* The end goal is to run this with Apply, so we can bring in a new fix added to an existing errorprone in one
* compilation. This will be introduced in a future PR. For the time being, disallow any other commands to be run with
* RemoveUnused.
*/
public final class RemoveUnusedModeInterference implements ModeInterference {
@Override
public ModeInterferenceResult interferesWith(Set<ModeName> modeNames) {
if (modeNames.contains(ModeName.REMOVE_UNUSED) && modeNames.size() > 1) {
return ModeInterferenceResult.notCompatible("%s cannot be used at the same time as any of %s"
.formatted(
ModeName.REMOVE_UNUSED.asGradlePropertyArgument(),
modeNames.stream()
.filter(Predicate.not(Predicate.isEqual(ModeName.REMOVE_UNUSED)))
.map(ModeName::asGradlePropertyArgument)
.collect(Collectors.joining(", "))));
}

return ModeInterferenceResult.noInterference();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* (c) Copyright 2025 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.gradle.suppressibleerrorprone.modes.modes;

import com.palantir.gradle.suppressibleerrorprone.modes.common.CommonOptions;
import com.palantir.gradle.suppressibleerrorprone.modes.common.Mode;
import com.palantir.gradle.suppressibleerrorprone.modes.common.ModifyCheckApiOption;
import com.palantir.gradle.suppressibleerrorprone.modes.common.PatchChecksOption;
import com.palantir.gradle.suppressibleerrorprone.modes.common.RemoveUnusedCheck;
import java.util.Map;

public final class RemoveUnusedMode implements Mode {
private static final String ALL_CHECKS = "";

public ModifyCheckApiOption modifyCheckApi() {
return ModifyCheckApiOption.mustModify();
}

@Override
public CommonOptions configureAndReturnCommonOptions(ModeOptionContext context) {
return new CommonOptions() {
@Override
public PatchChecksOption patchChecks() {
return PatchChecksOption.someChecks("RemoveUnusedSuppressions");
}

@Override
public Map<String, String> extraErrorProneCheckOptions() {
// Simplify the logic by only permitting blanket removal
return Map.of("SuppressibleErrorProne:RemoveUnusedSuppressions", ALL_CHECKS);
}

@Override
public RemoveUnusedCheck removeUnusedCheck() {
return RemoveUnusedCheck.ENABLED;
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ private void suppressCheckApi(File output) {
visitJar(output, (classJarPath, inputStream) -> classVisitorFor(classJarPath)
.map(classVisitorFactory -> {
ClassReader classReader = newClassReader(inputStream);
ClassWriter classWriter = new ClassWriter(classReader, 0);
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES);
ClassVisitor classVisitor = classVisitorFactory.apply(classWriter);

classReader.accept(classVisitor, 0);
Expand All @@ -91,6 +91,10 @@ && getParameters().getModifyVisitorState().get()) {
return Optional.of(VisitorStateClassVisitor::new);
}

if (classJarPath.equals("com/google/errorprone/bugpatterns/BugChecker$SuppressibleTreePathScanner.class")) {
return Optional.of(SuppressibleTreePathScannerClassVisitor::new);
}

return Optional.empty();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* (c) Copyright 2025 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.gradle.suppressibleerrorprone.transform;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

final class SuppressibleTreePathScannerClassVisitor extends ClassVisitor {
SuppressibleTreePathScannerClassVisitor(ClassVisitor classVisitor) {
super(Opcodes.ASM9, classVisitor);
}

@Override
public MethodVisitor visitMethod(
int access, String name, String descriptor, String signature, String[] exceptions) {
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);

if (name.equals("suppressed") && descriptor.equals("(Lcom/sun/source/tree/Tree;)Z")) {
return new SuppressedMethodVisitor(methodVisitor);
}

return methodVisitor;
}

private static final class SuppressedMethodVisitor extends MethodVisitor {
SuppressedMethodVisitor(MethodVisitor methodVisitor) {
super(Opcodes.ASM9, methodVisitor);
}

@Override
public void visitCode() {
super.visitCode();
// Load this (SuppressibleTreePathScanner instance)
mv.visitVarInsn(Opcodes.ALOAD, 0);
// Get the state field
mv.visitFieldInsn(
Opcodes.GETFIELD,
"com/google/errorprone/bugpatterns/BugChecker$SuppressibleTreePathScanner",
"state",
"Lcom/google/errorprone/VisitorState;");
// Check condition and potentially return false early
mv.visitMethodInsn(
Opcodes.INVOKESTATIC,
"com/palantir/suppressibleerrorprone/SuppressibleTreePathScannerModifications",
"shouldBypassSuppressions",
"(Lcom/google/errorprone/VisitorState;)Z",
false);

// If condition is true, return false immediately
Label continueLabel = new Label();
mv.visitJumpInsn(Opcodes.IFEQ, continueLabel);
mv.visitInsn(Opcodes.ICONST_0); // push false
mv.visitInsn(Opcodes.IRETURN);
mv.visitLabel(continueLabel);
// Add a frame here to fix verification
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
}
}
}
Loading