{"record":{"id":"54bb7619ab2678bd","repo":"didi/DoKit","slug":"maxstoredheapdumps-must-be-at-least-1","errorCode":null,"errorMessage":"maxStoredHeapDumps must be at least 1","messagePattern":"maxStoredHeapDumps must be at least 1","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java","lineNumber":67,"sourceCode":"  private static final String HPROF_SUFFIX = \".hprof\";\n  private static final String PENDING_HEAPDUMP_SUFFIX = \"_pending\" + HPROF_SUFFIX;\n\n  /** 10 minutes */\n  private static final int ANALYSIS_MAX_DURATION_MS = 10 * 60 * 1000;\n\n  private final Context context;\n  private final int maxStoredHeapDumps;\n\n  private volatile boolean writeExternalStorageGranted;\n  private volatile boolean permissionNotificationDisplayed;\n\n  public DefaultLeakDirectoryProvider(@NonNull Context context) {\n    this(context, DEFAULT_MAX_STORED_HEAP_DUMPS);\n  }\n\n  public DefaultLeakDirectoryProvider(@NonNull Context context, int maxStoredHeapDumps) {\n    if (maxStoredHeapDumps < 1) {\n      throw new IllegalArgumentException(\"maxStoredHeapDumps must be at least 1\");\n    }\n    this.context = context.getApplicationContext();\n    this.maxStoredHeapDumps = maxStoredHeapDumps;\n  }\n\n  @Override\n  public @NonNull\n  List<File> listFiles(@NonNull FilenameFilter filter) {\n    if (!hasStoragePermission()) {\n      requestWritePermissionNotification();\n    }\n    List<File> files = new ArrayList<>();\n\n    File[] externalFiles = externalStorageDirectory().listFiles(filter);\n    if (externalFiles != null) {\n      files.addAll(Arrays.asList(externalFiles));\n    }\n","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/didi/DoKit/blob/626827cddb2feb2f3aee87a52a064b4e5ca2bed4/Android/dokit-leakcanary/src/main/java/com/squareup/leakcanary/DefaultLeakDirectoryProvider.java#L49-L85","documentation":"DefaultLeakDirectoryProvider stores heap dumps in app storage and prunes old ones to stay under maxStoredHeapDumps. Its constructor validates that the cap is at least 1; zero or negative would mean heap dumps can never be persisted, so the constructor fails fast with IllegalArgumentException.","triggerScenarios":"Constructing new DefaultLeakDirectoryProvider(context, maxStoredHeapDumps) with 0 or a negative value — typically from a hardcoded config, a BuildConfig field, or a computed value (e.g. user preference mapped to 'keep no dumps') that was never validated.","commonSituations":"Teams wiring LeakCanary config from a remote-config or debug-settings screen where 0 is used to mean 'disable heap dumps'. Copy-pasted builder code with a placeholder 0. Using the DoraemonKit wrapper that exposes this provider with a configurable dump count.","solutions":["Pass a value >= 1; LeakCanary's own default is DEFAULT_MAX_STORED_HEAP_DUMPS (7)","If the intent was 'disable leak analysis', do not construct the provider — build a disabled RefWatcher (RefWatcher.DISABLED) instead","Clamp user-supplied settings: Math.max(1, configuredValue) at the configuration boundary"],"exampleFix":"// before\nnew DefaultLeakDirectoryProvider(context, 0);\n\n// after\nnew DefaultLeakDirectoryProvider(context, Math.max(1, config.maxHeapDumps));","handlingStrategy":"validation","validationCode":"int capped = Math.max(1, configuredMaxHeapDumps);\nnew DefaultLeakDirectoryProvider(context, capped);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Clamp user/remote-config values to >= 1 at the configuration boundary","Use RefWatcher.DISABLED to turn analysis off rather than a zero cap"],"tags":["leakcanary","android","validation","configuration","java"],"backgroundTag":null,"analyzedSha":"626827cddb2feb2f3aee87a52a064b4e5ca2bed4","analyzedAt":"2026-08-14T12:45:58.758Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}