skylot/jadx · error · JadxArgsValidateException

Threads count must be positive, got: {}

Error message

Threads count must be positive, got: {}

What it means

Thrown by DebugController.castType(ArgType) when converting a register's jadx ArgType to the RuntimeType accepted by the live debugger. The method only maps six kinds: INT, STRING, LONG, FLOAT, DOUBLE and OBJECT. Any other ArgType (BOOLEAN, BYTE, CHAR, SHORT, ARRAY, VOID, multi-dim arrays, etc.) reaches the final throw. It signals that the register the user tried to read/modify holds a type this debugger build cannot represent over JDWP, not a corrupt state.

Source

Thrown at jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java:450

		for (String fileName : files) {
			if (fileName.startsWith("-")) {
				throw new JadxArgsValidateException("Unknown option: " + fileName);
			}
		}
		return true;
	}

	private static void printFilesAndDirs(String defaultConfigFileName) {
		System.out.println("Files and directories used by jadx:");
		System.out.println(" - default config file: " + JadxCommonFiles.getConfigDir().resolve(defaultConfigFileName).toAbsolutePath());
		System.out.println(" - config directory:    " + JadxCommonFiles.getConfigDir().toAbsolutePath());
		System.out.println(" - cache directory:     " + JadxCommonFiles.getCacheDir().toAbsolutePath());
		System.out.println(" - temp directory:      " + JadxTempFiles.getTempRootDir().getParent().toAbsolutePath());
	}

	public void verify() {
		if (threadsCount <= 0) {
			throw new JadxArgsValidateException("Threads count must be positive, got: " + threadsCount);
		}
	}

	private static <T extends JadxCLIArgs> void saveConfig(T argsObj, @Nullable JadxConfigAdapter<T> configAdapter) {
		if (configAdapter == null) {
			throw new JadxRuntimeException("Config adapter set to null, can't save config");
		}
		configAdapter.useConfigRef(argsObj.saveConfig);
		configAdapter.save(argsObj);
		System.out.println("Config saved to " + configAdapter.getConfigPath().toAbsolutePath());
	}

	public JadxArgs toJadxArgs() {
		JadxArgs args = new JadxArgs();
		args.setInputFiles(files.stream().map(FileUtils::toFile).collect(Collectors.toList()));
		args.setOutDir(FileUtils.toFile(outDir));
		args.setOutDirSrc(FileUtils.toFile(outDirSrc));
		args.setOutDirRes(FileUtils.toFile(outDirRes));

View on GitHub (pinned to e738a26571)

Solutions

  1. Do not attempt to modify registers of unsupported primitive widths (boolean/byte/char/short/array); the debugger only edits int, long, float, double, String and object values.
  2. If you must change such a value, widen it at the source (rebuild the APK) or step to a point where the value lives in an int register.
  3. In jadx itself, extend castType to map the missing primitive kinds to their JDWP equivalents (BYTE->RuntimeType.BYTE etc.) and ensure RuntimeType supports them.
  4. Guard the UI path so unsupported types are disabled rather than reaching castType.

Example fix

// before
private RuntimeType castType(ArgType type) {
    if (type == ArgType.INT) return RuntimeType.INT;
    // ... STRING, LONG, FLOAT, DOUBLE, OBJECT
    throw new JadxRuntimeException("Unexpected type: " + type);
}

// after
private RuntimeType castType(ArgType type) {
    if (type == ArgType.INT) return RuntimeType.INT;
    if (type == ArgType.STRING) return RuntimeType.STRING;
    if (type == ArgType.LONG) return RuntimeType.LONG;
    if (type == ArgType.FLOAT) return RuntimeType.FLOAT;
    if (type == ArgType.DOUBLE) return RuntimeType.DOUBLE;
    if (type == ArgType.OBJECT) return RuntimeType.OBJECT;
    if (type == ArgType.BOOLEAN) return RuntimeType.BOOLEAN;
    if (type == ArgType.BYTE) return RuntimeType.BYTE;
    if (type == ArgType.CHAR) return RuntimeType.CHAR;
    if (type == ArgType.SHORT) return RuntimeType.SHORT;
    throw new JadxRuntimeException("Unsupported edit type: " + type);
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Guard before invoking the modify path that calls castType(ArgType):
private static final Set<ArgType> EDITABLE = EnumSet.noneOf(ArgType.class);
static {
    EDITABLE.add(ArgType.INT); EDITABLE.add(ArgType.STRING); EDITABLE.add(ArgType.LONG);
    EDITABLE.add(ArgType.FLOAT); EDITABLE.add(ArgType.DOUBLE); EDITABLE.add(ArgType.OBJECT);
}
boolean isEditableType(ArgType t) { return EDITABLE.contains(t); }
// call: if (!isEditableType(type)) { disableEditUi(); return; }

Type guard

static boolean isSupportedEditType(ArgType type) {
    return type == ArgType.INT || type == ArgType.STRING || type == ArgType.LONG
        || type == ArgType.FLOAT || type == ArgType.DOUBLE || type == ArgType.OBJECT;
}

Try / catch

// UI layer: degrade gracefully instead of crashing the debug session
RuntimeType rt;
try {
    rt = castType(type);
} catch (JadxRuntimeException e) {
    if (e.getMessage().startsWith("Unexpected type")) {
        LOG.info("Edit unsupported for type {}", type);
        return false;
    }
    throw e;
}

Prevention

When it happens

Trigger: Invoking register value inspection/modification (modifyValueInternal path) on a register whose ArgType is BOOLEAN, BYTE, CHAR, SHORT, ARRAY or any type outside the six handled branches. castType(type) is called to derive the RuntimeType sent to SmaliDebugger.setValueSync / value fetch.

Common situations: Editing a boolean or char field/register in the debugger UI; stopping on a method that uses byte/short locals; obfuscated code that widens primitive types. The debugger simply does not support editing those primitive widths.

Related errors


AI-assisted analysis of skylot/jadx@e738a26571 (2026-08-14). Data as JSON: /api/errors/00255a0386a60298. Report an issue: GitHub.