Skip to content

Catch #[no_mangle] #128

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

Merged
merged 21 commits into from
Dec 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ once_cell = "1.7.2" # polyfills a nightly feature
semver = "1.0.14"

# pgx core details
pgx = { version = "0.6.0-alpha.1" }
pgx-pg-config = { version = "0.6.0-alpha.1" }
pgx = { version = "=0.6.0-alpha.1" }
pgx-pg-config = { version = "=0.6.0-alpha.1" }

# language handler support
libloading = "0.7.2"
Expand All @@ -58,7 +58,7 @@ proc-macro2 = "1"
geiger = { version = "0.4", optional = true } # unsafe detection

[dev-dependencies]
pgx-tests = { version = "0.6.0-alpha.1" }
pgx-tests = { version = "=0.6.0-alpha.1" }
tempdir = "0.3.7"
once_cell = "1.7.2"
toml = "0.5.8"
Expand Down
4 changes: 3 additions & 1 deletion src/plrust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,10 @@ pub(crate) fn compile_function(fn_oid: pg_sys::Oid) -> eyre::Result<Output> {

let generated = unsafe { UserCrate::try_from_fn_oid(db_oid, fn_oid)? };
let provisioned = generated.provision(&work_dir)?;
// We want to introduce validation here.
let crate_dir = provisioned.crate_dir().to_path_buf();
let (built, output) = provisioned.build(pg_config, target_dir.as_path())?;
let (validated, _output) = provisioned.validate(pg_config, target_dir.as_path())?;
let (built, output) = validated.build(target_dir.as_path())?;
let shared_object = built.shared_object();

// store the shared object in our table
Expand Down
67 changes: 65 additions & 2 deletions src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -504,9 +504,7 @@ mod tests {
#[pg_test]
#[search_path(@extschema@)]
#[should_panic]
#[ignore]
fn plrust_block_unsafe_plutonium() {
// TODO: PL/Rust should defeat the latest in cutting-edge `unsafe` obfuscation tech
let definition = r#"
CREATE FUNCTION super_safe()
RETURNS text AS
Expand Down Expand Up @@ -754,6 +752,71 @@ mod tests {
));
assert!(our_id.is_none())
}

#[pg_test]
#[search_path(@extschema@)]
#[should_panic(expected = "error: declaration of a `no_mangle` static")]
fn plrust_block_unsafe_no_mangle() {
let definition = r#"
CREATE OR REPLACE FUNCTION no_mangle() RETURNS BIGINT
IMMUTABLE STRICT
LANGUAGE PLRUST AS
$$
#[no_mangle]
#[link_section = ".init_array"]
pub static INITIALIZE: &[u8; 136] = &GOGO;

#[no_mangle]
#[link_section = ".text"]
pub static GOGO: [u8; 136] = [
72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4,
36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72,
184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97,
80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80,
72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1,
230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210,
106, 59, 88, 15, 5,
];

Some(1)
$$;
"#;
Spi::run(definition);
let result = Spi::get_one::<i32>("SELECT no_mangle();\n");
assert_eq!(Some(1), result);
}

#[pg_test]
#[search_path(@extschema@)]
#[should_panic(expected = "error: declaration of a static with `link_section`")]
#[ignore] // TODO: raise MSRV
fn plrust_block_unsafe_link_section() {
let definition = r#"
CREATE OR REPLACE FUNCTION link_section() RETURNS BIGINT
IMMUTABLE STRICT
LANGUAGE PLRUST AS
$$
#[link_section = ".init_array"]
pub static INITIALIZE: &[u8; 136] = &GOGO;

#[link_section = ".text"]
pub static GOGO: [u8; 136] = [
72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 99, 104, 111, 46, 114, 105, 1, 72, 49, 4,
36, 72, 137, 231, 106, 1, 254, 12, 36, 72, 184, 99, 102, 105, 108, 101, 49, 50, 51, 80, 72,
184, 114, 47, 116, 109, 112, 47, 112, 111, 80, 72, 184, 111, 117, 99, 104, 32, 47, 118, 97,
80, 72, 184, 115, 114, 47, 98, 105, 110, 47, 116, 80, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80,
72, 184, 114, 105, 1, 44, 98, 1, 46, 116, 72, 49, 4, 36, 49, 246, 86, 106, 14, 94, 72, 1,
230, 86, 106, 19, 94, 72, 1, 230, 86, 106, 24, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210,
106, 59, 88, 15, 5,
];

Some(1)
$$;
"#;
Spi::run(definition);
let result = Spi::get_one::<i32>("SELECT link_section();\n");
assert_eq!(Some(1), result);
}
}

#[cfg(any(test, feature = "pg_test"))]
Expand Down
1 change: 1 addition & 0 deletions src/user_crate/crate_variant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use proc_macro2::{Ident, Span};
use quote::quote;

#[must_use]
#[derive(Clone)]
pub(crate) enum CrateVariant {
Function {
arguments: Vec<syn::FnArg>,
Expand Down
77 changes: 69 additions & 8 deletions src/user_crate/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
/*!
How to actually build and load a PL/Rust function
*/

/*
Consider opening the documentation like so:
```shell
cargo doc --no-deps --document-private-items --open
```
*/
mod crate_variant;
mod state_built;
mod state_generated;
mod state_loaded;
mod state_provisioned;
mod state_validated;
mod target;

// TODO: These past-tense names are confusing to reason about
// Consider rewriting them to present tense?
use crate_variant::CrateVariant;
pub(crate) use state_built::StateBuilt;
pub(crate) use state_generated::StateGenerated;
pub(crate) use state_loaded::StateLoaded;
pub(crate) use state_provisioned::StateProvisioned;
pub(crate) use state_validated::StateValidated;

use crate::PlRustError;
#[cfg(feature = "verify")]
Expand All @@ -23,8 +37,34 @@ use std::{
process::Output,
};

/**
Finite state machine with "typestate" generic

This forces `UserCrate<P>` to follow the linear path:
```rust
StateGenerated::try_from_$(inputs)_*
-> StateGenerated
-> StateProvisioned
-> StateValidated
-> StateBuilt
-> StateLoaded
```
Rust's ownership types allow guaranteeing one-way consumption.
*/
pub(crate) struct UserCrate<P: CrateState>(P);

/**
Stages of PL/Rust compilation

Each CrateState implementation has some set of fn including equivalents to
```rust
fn new(args: A) -> Self;
fn next(self, args: N) -> Self::NextCrateState;
```

These are currently not part of CrateState as they are type-specific and
premature abstraction would be unwise.
*/
pub(crate) trait CrateState {}

impl UserCrate<StateGenerated> {
Expand All @@ -51,9 +91,12 @@ impl UserCrate<StateGenerated> {
pub unsafe fn try_from_fn_oid(db_oid: pg_sys::Oid, fn_oid: pg_sys::Oid) -> eyre::Result<Self> {
unsafe { StateGenerated::try_from_fn_oid(db_oid, fn_oid).map(Self) }
}
/// Two functions exist internally, a `safe_lib_rs` and `unsafe_lib_rs`.
/// At first, it only has access to `StateGenerated::safe_lib_rs` due to the FSM.
#[tracing::instrument(level = "debug", skip_all)]
pub fn lib_rs(&self) -> eyre::Result<syn::File> {
self.0.lib_rs()
let (_, lib_rs) = self.0.safe_lib_rs()?;
Ok(lib_rs)
}
#[tracing::instrument(level = "debug", skip_all)]
pub fn cargo_toml(&self) -> eyre::Result<toml::value::Table> {
Expand All @@ -76,13 +119,13 @@ impl UserCrate<StateProvisioned> {
crate_dir = %self.0.crate_dir().display(),
target_dir = tracing::field::display(target_dir.display()),
))]
pub fn build(
pub fn validate(
self,
pg_config: PathBuf,
target_dir: &Path,
) -> eyre::Result<(UserCrate<StateBuilt>, Output)> {
) -> eyre::Result<(UserCrate<StateValidated>, Output)> {
self.0
.build(pg_config, target_dir)
.validate(pg_config, target_dir)
.map(|(state, output)| (UserCrate(state), output))
}

Expand All @@ -91,6 +134,23 @@ impl UserCrate<StateProvisioned> {
}
}

impl UserCrate<StateValidated> {
#[tracing::instrument(
level = "debug",
skip_all,
fields(
db_oid = %self.0.db_oid(),
fn_oid = %self.0.fn_oid(),
crate_dir = %self.0.crate_dir().display(),
target_dir = tracing::field::display(target_dir.display()),
))]
pub fn build(self, target_dir: &Path) -> eyre::Result<(UserCrate<StateBuilt>, Output)> {
self.0
.build(target_dir)
.map(|(state, output)| (UserCrate(state), output))
}
}

impl UserCrate<StateBuilt> {
#[tracing::instrument(level = "debug")]
pub(crate) fn built(
Expand Down Expand Up @@ -380,9 +440,8 @@ mod tests {

let generated_lib_rs = generated.lib_rs()?;
let fixture_lib_rs = parse_quote! {
#![deny(unsafe_op_in_unsafe_fn)]
#![forbid(unsafe_code)]
use pgx::prelude::*;
#[pg_extern]
fn #symbol_ident(arg0: &str) -> Option<String> {
Some(arg0.to_string())
}
Expand All @@ -408,7 +467,7 @@ mod tests {
crate-type = ["cdylib"]

[dependencies]
pgx = { version = "0.6.0-alpha.1", features = ["plrust"] }
pgx = { version = "=0.6.0-alpha.1", features = ["plrust"] }
pallocator = { version = "0.1.0", git = "https://github.com/tcdi/postgrestd", branch = "1.61" }
/* User deps added here */

Expand All @@ -427,7 +486,9 @@ mod tests {

let provisioned = generated.provision(&target_dir)?;

let (built, _output) = provisioned.build(pg_config, &target_dir)?;
let (validated, _output) = provisioned.validate(pg_config, &target_dir)?;

let (built, _output) = validated.build(&target_dir)?;

let _shared_object = built.shared_object();

Expand Down
1 change: 1 addition & 0 deletions src/user_crate/state_built.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub(crate) struct StateBuilt {

impl CrateState for StateBuilt {}

/// Available and ready-to-reload PL/Rust function
impl StateBuilt {
#[tracing::instrument(level = "debug", skip_all)]
pub(crate) fn new(
Expand Down
Loading