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

Implement undefined variable detection and prettify warning messages #468

Merged
merged 1 commit into from
Aug 19, 2021
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
2 changes: 1 addition & 1 deletion examples/full_example.sol
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ contract full_example {
function run_queue() public pure returns (uint16) {
uint16 count = 0;
// no initializer means its 0.
int32 n;
int32 n=0;

do {
if (get_pid_state(n) == State.Waiting) {
Expand Down
6 changes: 3 additions & 3 deletions src/bin/solang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ fn main() {
for filename in matches.values_of("INPUT").unwrap() {
let ns = solang::parse_and_resolve(filename, &mut cache, target);

diagnostics::print_messages(&ns, verbose);
diagnostics::print_messages(&cache, &ns, verbose);

if ns.contracts.is_empty() {
eprintln!("{}: error: no contracts found", filename);
Expand Down Expand Up @@ -381,10 +381,10 @@ fn process_filename(
}

if matches.is_present("STD-JSON") {
let mut out = diagnostics::message_as_json(&ns);
let mut out = diagnostics::message_as_json(&ns, &cache);
json.errors.append(&mut out);
} else {
diagnostics::print_messages(&ns, verbose);
diagnostics::print_messages(&cache, &ns, verbose);
}

if ns.contracts.is_empty() || diagnostics::any_errors(&ns.diagnostics) {
Expand Down
131 changes: 127 additions & 4 deletions src/codegen/cfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use super::{
constant_folding, dead_storage, expression::expression, reaching_definitions, strength_reduce,
vector_to_slice, Options,
};
use crate::codegen::undefined_variable;
use crate::parser::pt;
use crate::sema::ast::{
CallTy, Contract, Expression, Function, Namespace, Parameter, StringLocation, Type,
Expand Down Expand Up @@ -148,7 +149,117 @@ pub enum Instr {
Nop,
}

#[derive(Clone)]
impl Instr {
pub fn recurse_expressions<T>(
&self,
cx: &mut T,
f: fn(expr: &Expression, ctx: &mut T) -> bool,
) {
match self {
Instr::BranchCond { cond: expr, .. }
| Instr::Store { dest: expr, .. }
| Instr::LoadStorage { storage: expr, .. }
| Instr::ClearStorage { storage: expr, .. }
| Instr::Print { expr }
| Instr::AssertFailure { expr: Some(expr) }
| Instr::PopStorage { storage: expr, .. }
| Instr::AbiDecode { data: expr, .. }
| Instr::SelfDestruct { recipient: expr }
| Instr::Set { expr, .. } => {
expr.recurse(cx, f);
}

Instr::PushMemory { value: expr, .. } => {
expr.recurse(cx, f);
}

Instr::SetStorage { value, storage, .. }
| Instr::PushStorage { value, storage, .. } => {
value.recurse(cx, f);
storage.recurse(cx, f);
}

Instr::SetStorageBytes {
value,
storage,
offset,
} => {
value.recurse(cx, f);
storage.recurse(cx, f);
offset.recurse(cx, f);
}

Instr::Return { value: exprs } | Instr::Call { args: exprs, .. } => {
for expr in exprs {
expr.recurse(cx, f);
}
}

Instr::Constructor {
args,
value,
gas,
salt,
space,
..
} => {
for arg in args {
arg.recurse(cx, f);
}
if let Some(expr) = value {
expr.recurse(cx, f);
}
gas.recurse(cx, f);

if let Some(expr) = salt {
expr.recurse(cx, f);
}

if let Some(expr) = space {
expr.recurse(cx, f);
}
}

Instr::ExternalCall {
address,
payload,
value,
gas,
..
} => {
if let Some(expr) = address {
expr.recurse(cx, f);
}
payload.recurse(cx, f);
value.recurse(cx, f);
gas.recurse(cx, f);
}

Instr::ValueTransfer { address, value, .. } => {
address.recurse(cx, f);
value.recurse(cx, f);
}

Instr::EmitEvent { data, topics, .. } => {
for expr in data {
expr.recurse(cx, f);
}

for expr in topics {
expr.recurse(cx, f);
}
}

Instr::AssertFailure { expr: None }
| Instr::Unreachable
| Instr::Nop
| Instr::Branch { .. }
| Instr::PopMemory { .. } => {}
}
}
}

#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum InternalCallTy {
Static(usize),
Expand Down Expand Up @@ -649,6 +760,7 @@ impl ControlFlowGraph {
.collect::<Vec<String>>()
.join(", ")
),
Expression::Undefined(_) => "undef".to_string(),
_ => panic!("{:?}", expr),
}
}
Expand Down Expand Up @@ -1061,7 +1173,7 @@ pub fn generate_cfg(
cfg.selector = func.selector();
}

optimize(&mut cfg, ns, opt);
optimize_and_check_cfg(&mut cfg, ns, function_no, opt);

all_cfgs[cfg_no] = cfg;
}
Expand Down Expand Up @@ -1092,9 +1204,20 @@ fn resolve_modifier_call<'a>(
panic!("modifier should resolve to internal call");
}

/// Run codegen optimizer passess
pub fn optimize(cfg: &mut ControlFlowGraph, ns: &mut Namespace, opt: &Options) {
/// Detect undefined variables and run codegen optimizer passess
pub fn optimize_and_check_cfg(
cfg: &mut ControlFlowGraph,
ns: &mut Namespace,
func_no: Option<usize>,
opt: &Options,
) {
reaching_definitions::find(cfg);
if let Some(function) = func_no {
// If there are undefined variables, we raise an error and don't run optimizations
if undefined_variable::find_undefined_variables(cfg, ns, function) {
return;
}
}
if opt.constant_folding {
constant_folding::constant_folding(cfg, ns);
}
Expand Down
1 change: 1 addition & 0 deletions src/codegen/constant_folding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1083,6 +1083,7 @@ fn expression(
| Expression::FunctionArg(_, _, _) => (expr.clone(), true),
Expression::AllocDynamicArray(_, _, _, _)
| Expression::ReturnData(_)
| Expression::Undefined(_)
| Expression::FormatString { .. }
| Expression::InternalFunctionCfg(_) => (expr.clone(), false),
// nothing else is permitted in cfg
Expand Down
5 changes: 3 additions & 2 deletions src/codegen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ mod reaching_definitions;
mod statements;
mod storage;
mod strength_reduce;
mod undefined_variable;
mod unused_variable;
mod vector_to_slice;

use self::cfg::{optimize, ControlFlowGraph, Instr, Vartable};
use self::cfg::{optimize_and_check_cfg, ControlFlowGraph, Instr, Vartable};
use self::expression::expression;
use crate::sema::ast::{Layout, Namespace};
use crate::sema::contracts::visit_bases;
Expand Down Expand Up @@ -132,7 +133,7 @@ fn storage_initializer(contract_no: usize, ns: &mut Namespace, opt: &Options) ->

cfg.vars = vartab.drain();

optimize(&mut cfg, ns, opt);
optimize_and_check_cfg(&mut cfg, ns, None, opt);

cfg
}
Expand Down
32 changes: 15 additions & 17 deletions src/codegen/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn statement(
}
}
Statement::VariableDecl(loc, pos, _, Some(init)) => {
if should_remove_variable(pos, func, Some(init)) {
if should_remove_variable(pos, func) {
let mut params = SideEffectsCheckParameters {
cfg,
contract_no,
Expand All @@ -57,7 +57,6 @@ pub fn statement(
}

let expr = expression(init, cfg, contract_no, Some(func), ns, vartab);

cfg.add(
vartab,
Instr::Set {
Expand All @@ -68,21 +67,19 @@ pub fn statement(
);
}
Statement::VariableDecl(loc, pos, param, None) => {
if should_remove_variable(pos, func, None) {
if should_remove_variable(pos, func) {
return;
}

// Set it to a default value
if let Some(expr) = param.ty.default(ns) {
cfg.add(
vartab,
Instr::Set {
loc: *loc,
res: *pos,
expr,
},
);
}
// Add variable as undefined
cfg.add(
vartab,
Instr::Set {
loc: *loc,
res: *pos,
expr: Expression::Undefined(param.ty.clone()),
},
);
}
Statement::Return(_, values) => {
if let Some(return_instr) = return_override {
Expand Down Expand Up @@ -787,7 +784,7 @@ fn destructure(
let expr = cast(&param.loc, right, &param.ty, true, ns, &mut Vec::new())
.expect("sema should have checked cast");

if should_remove_variable(res, func, Some(&expr)) {
if should_remove_variable(res, func) {
continue;
}

Expand Down Expand Up @@ -1219,7 +1216,9 @@ impl Type {
)),
None,
)),
Type::InternalFunction { .. } | Type::ExternalFunction { .. } => None,
Type::InternalFunction { .. } | Type::Contract(_) | Type::ExternalFunction { .. } => {
None
}
Type::Array(_, dims) => {
if dims[0].is_none() {
Some(Expression::AllocDynamicArray(
Expand All @@ -1241,7 +1240,6 @@ impl Type {
))
}
}
Type::Contract(_) => None,
_ => unreachable!(),
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/codegen/strength_reduce.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1690,6 +1690,14 @@ fn expression_values(expr: &Expression, vars: &Variables, ns: &Namespace) -> Has
// reference to a function; ignore
HashSet::new()
}
Expression::Undefined(expr_type) => {
// If the variable is undefined, we can return the default value to optimize operations
if let Some(default_expr) = expr_type.default(ns) {
return expression_values(&default_expr, vars, ns);
}

HashSet::new()
}
e => {
let ty = e.ty();
let mut set = HashSet::new();
Expand Down
Loading