Skip to content

Commit 0fa857a

Browse files
authored
fix: Handle inputs of moderate size and up (#91)
Handles inputs of moderate size and up by ensuring that the input byte array is fully cloned before parsing it as json.
1 parent 979fa9e commit 0fa857a

13 files changed

Lines changed: 156 additions & 27 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ tests/modules/rust_wasm32_filter/pkg
1919
tests/modules/rust_wasm32_normalize/Cargo.lock
2020
tests/modules/rust_wasm32_normalize/target
2121
tests/modules/rust_wasm32_normalize/pkg
22+
tests/modules/rust_wasm32_memory/Cargo.lock
23+
tests/modules/rust_wasm32_memory/target
24+
tests/modules/rust_wasm32_memory/pkg
2225
sdk-rust/target
2326
sdk-rust/Cargo.lock
2427
host-go/build/host-go.exe

sdk-rust/Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "lens_sdk"
33
description = "An SDK for writing bi-directional, wasm based, LensVM transformations in Rust"
4-
version = "0.6.0"
4+
version = "0.7.0"
55
edition = "2021"
66
readme = "../README.md"
77
repository = "https://github.com/lens-vm/lens"
@@ -12,5 +12,5 @@ crate-type = ["lib"]
1212

1313
[dependencies]
1414
serde = "1.0"
15-
serde_json = "1.0.87"
16-
byteorder = "1.4.3"
15+
serde_json = "1.0"
16+
byteorder = "1.4"

sdk-rust/src/lib.rs

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ pub fn free(ptr: *mut u8, size: usize) {
8282
Vec::from_raw_parts(ptr, size, size)
8383
};
8484
let buf = ManuallyDrop::new(buf);
85-
mem::drop(ManuallyDrop::into_inner(buf));
85+
ManuallyDrop::into_inner(buf);
8686
}
8787

8888
/// Manually drop the memory occupied by a transport buffer at the given location.
@@ -116,15 +116,15 @@ pub fn free_transport_buffer(ptr: *mut u8) -> Result<()> {
116116

117117
let type_id: i8 = type_rdr.read_i8()?;
118118
if type_id == NIL_TYPE_ID {
119-
mem::drop(ManuallyDrop::into_inner(type_rdr));
119+
ManuallyDrop::into_inner(type_rdr);
120120
return Ok(())
121121
}
122122
if type_id == EOS_TYPE_ID {
123-
mem::drop(ManuallyDrop::into_inner(type_rdr));
123+
ManuallyDrop::into_inner(type_rdr);
124124
return Ok(())
125125
}
126126
if type_id < 0 {
127-
mem::drop(ManuallyDrop::into_inner(type_rdr));
127+
ManuallyDrop::into_inner(type_rdr);
128128
return Ok(())
129129
}
130130

@@ -137,7 +137,7 @@ pub fn free_transport_buffer(ptr: *mut u8) -> Result<()> {
137137

138138
let len: usize = len_rdr.read_u32::<LittleEndian>()?.try_into()?;
139139

140-
free(ptr, len);
140+
free(ptr, mem::size_of::<i8>()+mem::size_of::<u32>()+len);
141141

142142
Ok(())
143143
}
@@ -162,51 +162,49 @@ pub fn free_transport_buffer(ptr: *mut u8) -> Result<()> {
162162
///
163163
/// This function will return an [Error](error/enum.Error.html) if the data at the given location is not in the expected
164164
/// format.
165-
pub fn try_from_mem<TOutput: for<'a> Deserialize<'a>>(ptr: *mut u8) -> Result<StreamOption<TOutput>> {
165+
pub fn try_from_mem<TOutput: for<'a> Deserialize<'a> + Clone>(ptr: *mut u8) -> Result<StreamOption<TOutput>> {
166166
let type_vec: Vec<u8> = unsafe {
167167
Vec::from_raw_parts(ptr, mem::size_of::<i8>(), mem::size_of::<i8>())
168168
};
169169

170-
let type_rdr = Cursor::new(type_vec);
171-
let mut type_rdr = ManuallyDrop::new(type_rdr);
170+
let mut type_rdr = Cursor::new(type_vec.clone());
171+
let _ = ManuallyDrop::new(type_vec);
172172

173173
let type_id: i8 = type_rdr.read_i8()?;
174174
if type_id == NIL_TYPE_ID {
175-
mem::drop(ManuallyDrop::into_inner(type_rdr));
175+
free_transport_buffer(ptr)?;
176176
return Ok(StreamOption::None)
177177
}
178178
if type_id == EOS_TYPE_ID {
179-
mem::drop(ManuallyDrop::into_inner(type_rdr));
179+
free_transport_buffer(ptr)?;
180180
return Ok(StreamOption::EndOfStream)
181181
}
182182
if type_id < 0 {
183-
mem::drop(ManuallyDrop::into_inner(type_rdr));
183+
free_transport_buffer(ptr)?;
184184
return Result::from(error::LensError::InputErrorUnsupportedError)
185185
}
186186

187187
let len_vec: Vec<u8> = unsafe {
188188
Vec::from_raw_parts(ptr.add(mem::size_of::<i8>()), mem::size_of::<u32>(), mem::size_of::<u32>())
189189
};
190190

191-
let len_rdr = Cursor::new(len_vec);
192-
let mut len_rdr = ManuallyDrop::new(len_rdr);
191+
let mut len_rdr = Cursor::new(len_vec.clone());
192+
let _ = ManuallyDrop::new(len_vec);
193193

194194
let len: usize = len_rdr.read_u32::<LittleEndian>()?.try_into()?;
195195

196196
let input_vec: Vec<u8> = unsafe {
197197
Vec::from_raw_parts(ptr.add(mem::size_of::<i8>()+mem::size_of::<u32>()), len, len)
198198
};
199-
let input_vec = ManuallyDrop::new(input_vec);
200-
201-
let json_string = String::from_utf8(input_vec.to_vec())?.clone();
202199

203200
// It is possible for null json values to reach this line, particularly if sourced directly
204201
// from a 3rd party module, so we ensure that we parse to option as well as the earlier type_id
205202
// checks.
206-
let result = match serde_json::from_str::<Option<TOutput>>(&json_string)? {
207-
Some(v) => StreamOption::Some(v),
203+
let result = match serde_json::from_slice::<Option<TOutput>>(&input_vec.clone())? {
204+
Some(v) => StreamOption::Some(v.clone()),
208205
None => StreamOption::None,
209206
};
207+
let _ = ManuallyDrop::new(input_vec);
210208

211209
Ok(result)
212210
}
@@ -236,7 +234,8 @@ pub fn try_to_mem(type_id: i8, message: &[u8]) -> Result<*mut u8> {
236234
}
237235
};
238236

239-
let result = wtr.into_inner().clone().as_mut_ptr();
237+
let mut buffer = wtr.into_inner().clone();
238+
let result = buffer.as_mut_ptr();
240239
Ok(result)
241240
}
242241

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package integration
2+
3+
import (
4+
"testing"
5+
6+
"github.com/lens-vm/lens/tests/modules"
7+
)
8+
9+
// Rust is very aggressive in cleaning up memory, and we had a bug where lenses would fail if the input
10+
// was particularly large, and/or the transform copied the input too many times.
11+
func TestWithMem(t *testing.T) {
12+
type Value struct {
13+
Name string
14+
Type string `json:"__type"`
15+
// Inline arrays seem to be particularly problematic, I guess it is due to some lifetime stuff
16+
// in the serde library.
17+
Array []string
18+
}
19+
20+
executeTest(
21+
t,
22+
TestCase[Value, Value]{
23+
LensFile: `
24+
{
25+
"lenses": [
26+
{
27+
"path": "` + modules.WasmPath_Memory + `"
28+
}
29+
]
30+
}`,
31+
Input: []Value{
32+
{
33+
Name: "John",
34+
Type: "passsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss",
35+
Array: []string{},
36+
},
37+
},
38+
ExpectedOutput: []Value{
39+
{
40+
Name: "John",
41+
Type: "passsssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss",
42+
Array: []string{},
43+
},
44+
},
45+
},
46+
)
47+
}

tests/modules/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ build:
1111
cargo build --target wasm32-unknown-unknown --manifest-path "./rust_wasm32_counter/Cargo.toml"
1212
cargo build --target wasm32-unknown-unknown --manifest-path "./rust_wasm32_filter/Cargo.toml"
1313
cargo build --target wasm32-unknown-unknown --manifest-path "./rust_wasm32_normalize/Cargo.toml"
14+
cargo build --target wasm32-unknown-unknown --manifest-path "./rust_wasm32_memory/Cargo.toml"
1415
(cd "./as_wasm32_simple/" && npm install && npm run asbuild:debug)

tests/modules/rust_wasm32_counter/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extern "C" {
1010
fn next() -> *mut u8;
1111
}
1212

13-
#[derive(Serialize, Deserialize)]
13+
#[derive(Serialize, Deserialize, Clone)]
1414
pub struct Value {
1515
#[serde(rename = "Id")]
1616
pub id: usize,

tests/modules/rust_wasm32_filter/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ extern "C" {
88
fn next() -> *mut u8;
99
}
1010

11-
#[derive(Serialize, Deserialize)]
11+
#[derive(Serialize, Deserialize, Clone)]
1212
pub struct Value {
1313
#[serde(rename = "Name")]
1414
pub name: String,
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "rust-wasm32-memory"
3+
version = "0.1.0"
4+
edition = "2018"
5+
6+
[lib]
7+
crate-type = ["cdylib"]
8+
9+
[dependencies]
10+
serde = { version = "1.0", features = ["derive"] }
11+
serde_json = "1.0"
12+
lens_sdk = { path = "../../../sdk-rust" }
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// This Source Code Form is subject to the terms of the Mozilla Public
2+
// License, v. 2.0. If a copy of the MPL was not distributed with this
3+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
5+
use std::error::Error;
6+
use serde::{Deserialize, Serialize};
7+
use lens_sdk::StreamOption;
8+
use lens_sdk::option::StreamOption::{Some, None, EndOfStream};
9+
10+
#[link(wasm_import_module = "lens")]
11+
extern "C" {
12+
fn next() -> *mut u8;
13+
}
14+
15+
#[no_mangle]
16+
pub extern "C" fn alloc(size: usize) -> *mut u8 {
17+
lens_sdk::alloc(size)
18+
}
19+
20+
#[no_mangle]
21+
pub extern "C" fn transform() -> *mut u8 {
22+
match try_transform() {
23+
Ok(o) => match o {
24+
Some(result_json) => lens_sdk::to_mem(lens_sdk::JSON_TYPE_ID, &result_json),
25+
None => lens_sdk::nil_ptr(),
26+
EndOfStream => lens_sdk::to_mem(lens_sdk::EOS_TYPE_ID, &[]),
27+
},
28+
Err(e) => lens_sdk::to_mem(lens_sdk::ERROR_TYPE_ID, &e.to_string().as_bytes())
29+
}
30+
}
31+
32+
#[derive(Serialize, Deserialize, Clone)]
33+
pub struct Input {
34+
#[serde(rename = "Name")]
35+
pub name: String,
36+
#[serde(rename= "__type")]
37+
pub __type: String,
38+
#[serde(rename = "Array")]
39+
pub array: Vec<String>,
40+
}
41+
42+
fn try_transform() -> Result<StreamOption<Vec<u8>>, Box<dyn Error>> {
43+
let ptr = unsafe { next() };
44+
let mut input = match lens_sdk::try_from_mem::<Input>(ptr)? {
45+
Some(v) => v,
46+
// Implementations of `transform` are free to handle nil however they like. In this
47+
// implementation we chose to return nil given a nil input.
48+
None => return Ok(None),
49+
EndOfStream => return Ok(EndOfStream)
50+
};
51+
52+
for _ in 1..1000 {
53+
// Copy the input a bunch of times to make sure that the memory is not cleaned up
54+
// before we are finished with it.
55+
input = input.clone();
56+
}
57+
58+
let result_json = serde_json::to_vec(&input.clone())?;
59+
lens_sdk::free_transport_buffer(ptr)?;
60+
Ok(Some(result_json))
61+
}

tests/modules/rust_wasm32_normalize/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extern "C" {
1111
fn next() -> *mut u8;
1212
}
1313

14-
#[derive(Serialize, Deserialize)]
14+
#[derive(Serialize, Deserialize, Clone)]
1515
pub struct Book {
1616
#[serde(rename = "Name")]
1717
pub name: String,

0 commit comments

Comments
 (0)