Skip to content

[Bug]: App - CommandLine.addArguments() handleQuoting breaks App.open() when arguments contain spaces #230

Description

@adriancostin6

Summary

When I try opening my Oculix App, CommandLine messes up the quoting that reaches ProcessBuilder and the App fails to open. A reproducer is included in the Additional context section.

Steps to reproduce

1. Set up the reproducer below on your Linux machine
2. Run the run.sh reproducer script provided in Additional context

Expected behavior

App opens without having to do dirty tricks behind the scenes to disable handleQuoting.

Actual behavior

App does not open, I have to do reflection to disable the quoting to make it open.

Minimal reproducer (script)

Operating system

Ubuntu / Debian

Java version

openjdk 21.0.9 2025-10-21 LTS

OculiX version / artifact

oculixapi-3.0.3-rc4-linux.jar

Where does the bug happen?

API (Screen / Region / Pattern / Match)

Logs / console output

Additional context

handleQuoting defaults to true when we call addArguments(String) in the Oculix App constructor:

cmd.addArguments(arguments);

This, in turn leads to broken handling of quoting before passing certain commands to process builder. In my use-case, I could reproduce this fairly easily.

I have created a small reproducer for the handleQuoting problem. The main components of this are:

  • Simple.java: A small Java application that spawns a Swing GUI when a file is passed as an argument
  • SImpleProcessBuilder.java: Uses process builder to start the Simple.java program using java, passing a file as an argument
  • SimpleOculixApp.java: Uses the Oculix API to start the same Simple.java program using java, passing a file as an argument
  • run.sh: shell script that automates building and running the reproducer. this compiles and bundles Simple.java as a jar, then downloads the Oculix API RC4 for Linux and runs SimpleProcessBuilder.java, SimpleOculixApp and SimpleOculixApp + reflection enabled via a CLI parameter. it should handle everything, but feel free to adapt if it breaks on your machine.

As stated above, the Java classes for SimpleOculixApp and SimpleProcessBuilder start the Simple.java program using the created JAR. They do so using java, so make sure that it is in PATH and ready to go.

That being said, the runner script creates a directory that contains spaces in the path to trigger the BUG. It moves the created JAR file there, and creates an extra argfile and a dummy file. These will be later used as arguments to the java command that will run SimpleOculixApp and SimpleProcessBuilder to match my exact use-case. By doing so, I hoped to highlight the way in which handleQuoting will prevent apps from opening when hitting this scenario.

I will attach the reproducer files below, but also paste them as code-blocks, in case one is easier to use than the other.

SimpleProcessBuilder.java
Simple.java
SimpleOculixApp.java
run.sh

#!/usr/bin/env bash

# run.sh

cleanup() {
    rm -f manifest.mf
    rm -f *.jar
    rm -f *.class
    rm -rf "spa ces"
}
trap "cleanup" EXIT

mkdir -p "spa ces"
echo "--add-opens=java.base/java.io=ALL-UNNAMED" >> "spa ces/some.argfile"
echo "bla bla bla" > "spa ces/some.file"

# Step 1. Compile a simple java JAR with a Swing app
echo "Compiling a simple Swing app, used for testing..."
javac Simple.java
echo "Main-Class: Simple" > manifest.mf
jar -cvmf manifest.mf simple.jar Simple.class
mv simple.jar "spa ces"

# Step 2. Get Oculix API and compile the reproducer
# Start SimpleProcessBuilder
echo "Running SimpleProcessBuilder..."
echo "Proves that passing arguments even with no quoting as cmd array works."
javac SimpleProcessBuilder.java
java -cp . SimpleProcessBuilder # should open app just fine
## Start SimpleOculixApp
wget https://github.com/oculix-org/Oculix/releases/download/v3.0.3-rc4/oculixapi-3.0.3-rc4-linux.jar
javac -cp oculixapi-3.0.3-rc4-linux.jar SimpleOculixApp.java
echo "Running SimpleOculixApp without reflection..."
echo "Proves that the app won't open due to broken handleQuoting."
java -cp ".:oculixapi-3.0.3-rc4-linux.jar" SimpleOculixApp
echo "Running SimpleOculixApp with reflection to disable broken handleQuoting..."
echo "Proves that app opens if we do manual quoting for parameters instead of handleQuoting."
java -cp ".:oculixapi-3.0.3-rc4-linux.jar" SimpleOculixApp dirtyReflection
import javax.swing.*;
import java.awt.*;
import java.io.File;

// Simple.java

public class Simple {
    public static void main(String[] args) {
        System.out.println("Argument count: " + args.length);
        for (int i = 0; i < args.length; i++) {
            System.out.println("Argument " + i + ": " + args[i]);
        }

        String guiUnlocker = args[0];
        File f = new File(guiUnlocker);
        if(f.isFile()) { 
            System.out.println("File found, opening GUI.");

            JFrame frame = new JFrame("Simple");
            frame.setPreferredSize(new Dimension(1000, 800));

            JPanel panel = new JPanel();
            JLabel label = new JLabel("Hello, World!");
            panel.add(label);
            frame.add(panel);

            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.pack();
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        } else {
            System.out.println("File not found, no GUI for you.");
        }

    }
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

// SimpleProcessBuilder.java

public class SimpleProcessBuilder {
    public static void main(String[] args) {
        ProcessBuilder pb = new ProcessBuilder(
                "java",
                "@spa ces/some.argfile",
                "-cp",
                "spa ces/simple.jar",
                "Simple",
                "spa ces/some.file"
        );

        // Merge stderr into stdout so we capture both
        pb.redirectErrorStream(true);

        try {
            Process process = pb.start();

            // Read and print all output
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println(line);
                }
            }

            // Wait for process to complete and get exit code
            int exitCode = process.waitFor();
            System.out.println("Process exited with code: " + exitCode);
        } catch (IOException | InterruptedException e) {
            System.err.println("IOException: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
import org.sikuli.script.App;

import org.apache.commons.exec.CommandLine;
import java.lang.reflect.Field;
import java.util.List;

// SimpleOculixApp.java

public class SimpleOculixApp {
    public static void main(String[] args) {
        App app = new App(
            "java",
            "\"@spa ces/some.argfile\" -cp \"spa ces/simple.jar\" Simple \"spa ces/some.file\""
        );

        if (args.length > 0) {
            System.out.println("Using reflection to disable handleQuoting...");
            try {
                Field cmdField = app.getClass().getDeclaredField("cmd");
                cmdField.setAccessible(true);
                CommandLine commandLine = (CommandLine) cmdField.get(app);
                Field argumentsField = commandLine.getClass().getDeclaredField("arguments");
                argumentsField.setAccessible(true);
                List<Object> arguments = (List<Object>) argumentsField.get(commandLine);
                for (Object argument : arguments) {
                    Field handleQuotingField = argument.getClass().getDeclaredField("handleQuoting");
                    handleQuotingField.setAccessible(true);
                    handleQuotingField.set(argument, Boolean.FALSE);
                }
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }

        app.reset();
        if(app.open()) {
            System.out.println("App opened successfully");
        } else {
            System.out.println("Failed to open app");
        }
    }
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions