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

Recursive Ref Causes "String index out of range" #141

Open
jawaff opened this issue Nov 19, 2024 · 2 comments
Open

Recursive Ref Causes "String index out of range" #141

jawaff opened this issue Nov 19, 2024 · 2 comments
Assignees
Labels
bug Something isn't working
Milestone

Comments

@jawaff
Copy link

jawaff commented Nov 19, 2024

Version

I'm using 4.5.11 of the vertx-openapi library, which off course transitively depends on vertx-json-schema.

Context

I have openapi files that have $refs to separate json schema files (using draft 4 currently), which are provided to the OpenAPIContract::fromContract by way of the additionalContractFiles argument. That's kind of unrelated, but the important detail is that I have an OpenAPI file with a $ref to a schema that defines a tree structure. That tree schema for the sake of simplicity can be reduced to the following to cause an exception:

type: object
properties:
  children:
    type: array
      $ref: '#'

The exception in question is this:

Caused by: java.lang.StringIndexOutOfBoundsException: String index out of range: 0
	at java.base/java.lang.StringLatin1.charAt(StringLatin1.java:47)
	at java.base/java.lang.String.charAt(String.java:693)
	at io.vertx.json.schema.impl.JsonRef.resolveUri(JsonRef.java:326)
	at io.vertx.json.schema.impl.JsonRef.resolve(JsonRef.java:211)
	at io.vertx.json.schema.impl.JsonRef.resolveUri(JsonRef.java:338)
	at io.vertx.json.schema.impl.JsonRef.resolve(JsonRef.java:211)
	at io.vertx.json.schema.impl.JsonRef.resolveUri(JsonRef.java:338)
	at io.vertx.json.schema.impl.JsonRef.resolve(JsonRef.java:211)
	at io.vertx.json.schema.impl.SchemaRepositoryImpl.resolve(SchemaRepositoryImpl.java:243)
	at io.vertx.openapi.contract.OpenAPIVersion.lambda$resolve$4(OpenAPIVersion.java:110)
	at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$4(ContextImpl.java:192)
	at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:270)
	at io.vertx.core.impl.ContextImpl$1.execute(ContextImpl.java:221)
	at io.vertx.core.impl.WorkerTask.run(WorkerTask.java:56)
	at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:81)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)

Do you have a reproducer?

I do not have a reproducer, but the problem is pretty obvious if you look at the JsonRef::resolveUri function. There is no isEmpty check on the path before accessing the first character.

Steps to reproduce

Look at this if statement and try to tell me that there's a guarantee that path will always be nonempty. If you have a $ref: '#', then the parts array is going to initialized as ["#", ""] and you'll run into the exception above at the linked if statement because the second element is empty.

The temporary solution to fix this was to change my ref to $ref: '#/'. It provides the same behavior as the '#' by itself, but just the pound symbol should be supported and I'm not sure why the existing tests aren't triggering the same exception that I'm running into.

Solutions

There seems to be multiple solutions to fix this, but in the end a pound symbol by itself should just trigger anchors.get(prefix); in the end to retrieve the current schema. Both solutions below should result in the same behavior and both changes are to the JsonRef::resolveUri function.

Option 1:

We could make hashPresent set to false when the passed uri is "#".

    final boolean hashPresent = parts.length == 2 && parts[1] != null && !parts[1].isEmpty();

Option 2:

We could add a safety check before accessing the first character of the path.

    if (hashPresent && !path.isEmpty() && path.charAt(0) != '/') {

Extra

  • Anything that can be relevant such as OS version, JVM version
@jawaff jawaff added the bug Something isn't working label Nov 19, 2024
@jawaff
Copy link
Author

jawaff commented Nov 20, 2024

I take it back, $ref: '#/' is not a solution because it tries to look for a schema at URI <currentSchemaFileUri>#/ in the lookup table, I believe. That works in the initial validation that the schemas are valid according to the json schema specification, but I'm running into another issue when validating json with against the schema with the recursive reference.

The exception I'm getting there is below. Maybe that portion of code relies on the $ref: '#' being used.

io.vertx.json.schema.SchemaException: Unresolved $ref #/: Absolute URI <relevantSchemaUri>#/
Known schemas:
<big list of schemas in the lookup table>
at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:200)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:735)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:553)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:204)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:735)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:553)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:204)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:322)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:204)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:352)
	at io.vertx.json.schema.impl.SchemaValidatorImpl.validate(SchemaValidatorImpl.java:54)
	at io.vertx.openapi.validation.impl.RequestValidatorImpl.validateBody(RequestValidatorImpl.java:156)
	at io.vertx.openapi.validation.impl.RequestValidatorImpl.lambda$validate$2(RequestValidatorImpl.java:106)
	at io.vertx.core.impl.ContextImpl.lambda$executeBlocking$5(ContextImpl.java:205)
	at io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:270)
	at io.vertx.core.impl.ContextImpl$1.execute(ContextImpl.java:221)
	at io.vertx.core.impl.WorkerTask.run(WorkerTask.java:56)
	at io.vertx.core.impl.TaskQueue.run(TaskQueue.java:81)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)

@vietj vietj added this to the 4.5.12 milestone Nov 21, 2024
@vietj
Copy link
Member

vietj commented Nov 21, 2024

@CheesyBoy123 assigned to you

@vietj vietj modified the milestones: 4.5.12, 4.5.13 Jan 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants