From: Timm Bäder Date: Sat, 5 Nov 2016 17:33:41 +0000 (+0100) Subject: Add nullability checker X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2c15aebc98a3be655815a0467d3a6b49be90cc9e;p=thirdparty%2Fvala.git Add nullability checker --- diff --git a/vala/Makefile.am b/vala/Makefile.am index 397594cdc..80bc3ea12 100644 --- a/vala/Makefile.am +++ b/vala/Makefile.am @@ -103,6 +103,7 @@ libvalacore_la_VALASOURCES = \ valamethodtype.vala \ valanamedargument.vala \ valanamespace.vala \ + valanullabilitychecker.vala \ valanullliteral.vala \ valanulltype.vala \ valaobjectcreationexpression.vala \ diff --git a/vala/valabasicblock.vala b/vala/valabasicblock.vala index 1cdb5a60d..51214ac8d 100644 --- a/vala/valabasicblock.vala +++ b/vala/valabasicblock.vala @@ -27,7 +27,7 @@ using GLib; * jumps or jump targets. */ public class Vala.BasicBlock { - private List nodes = new ArrayList (); + public List nodes = new ArrayList (); // control flow graph private List predecessors = new ArrayList (); @@ -35,7 +35,7 @@ public class Vala.BasicBlock { // dominator tree public BasicBlock parent { get; private set; } - List children = new ArrayList (); + public List children = new ArrayList (); Set df = new HashSet (); Set phi_functions = new HashSet (); diff --git a/vala/valaflowanalyzer.vala b/vala/valaflowanalyzer.vala index 416144a65..4f44177f5 100644 --- a/vala/valaflowanalyzer.vala +++ b/vala/valaflowanalyzer.vala @@ -227,6 +227,10 @@ public class Vala.FlowAnalyzer : CodeVisitor { } analyze_body (m.entry_block); + + // Now that we built up the CFG, we can actually use it + var nullability_checker = new NullabilityChecker (context); + nullability_checker.check (m.entry_block); } void analyze_body (BasicBlock entry_block) { diff --git a/vala/valanullabilitychecker.vala b/vala/valanullabilitychecker.vala new file mode 100644 index 000000000..1693145d7 --- /dev/null +++ b/vala/valanullabilitychecker.vala @@ -0,0 +1,123 @@ + + + +public class Vala.NullabilityChecker : CodeVisitor { + private const bool debug = false; + private CodeContext context; + private BasicBlock root_block; + private BasicBlock current_block; + + + public NullabilityChecker (CodeContext context) { + this.context = context; + } + + public void check (BasicBlock root_block) { + this.root_block = root_block; + this.current_block = root_block; + + if (debug) { + message ("=========================================="); + root_block.print (); + message ("=========================================="); + } + this.visit_basic_block (root_block); + } + + private void visit_basic_block (BasicBlock block) { + if (debug) { + message ("Checking Basic Block %s (%d nulls, %d non-nulls)", + block.name, block.null_vars.size, block.non_null_vars.size); + message ("Accept() for %d nodes", block.nodes.size); + } + + this.current_block = block; + + foreach (var node in block.nodes) { + //message ("Accept() ing %s", node.type_name); + node.accept (this); + } + + foreach (var child in block.children) { + visit_basic_block (child); + } + + + // TODO: reset current_block ??? + } + + public override void visit_member_access (MemberAccess access) { + if (debug) + message ("Checking Member Access %s", access.to_string ()); + if (access.inner != null) { + if (debug) + message ("Inner: %s", access.inner.to_string ()); + bool is_null = false; + bool is_non_null = false; + BasicBlock? b = current_block; + while (b != null) { + if (b.null_vars.contains (access.inner.symbol_reference)) { + is_null = true; + break; + } else if (b.non_null_vars.contains (access.inner.symbol_reference)) { + is_non_null = true; + break; + } + b = b.parent; + } + + if (is_null) { + Report.error (access.source_reference, "`%s' is null here".printf (access.to_string ())); + } else if (access.inner.symbol_reference != null) { + DataType? symbol_type = context.analyzer.get_value_type_for_symbol (access.inner.symbol_reference, false); + if (symbol_type != null) { + // If the referenced type is nullable, and the code didn't make sure the reference + // is not null, we need to error out here. + if (symbol_type.nullable && !is_non_null) { + Report.error (access.source_reference, "Access to nullable reference `%s' denied".printf (access.inner.symbol_reference.get_full_name ())); + } + } + } + } + + access.accept_children (this); + } + + public override void visit_declaration_statement (DeclarationStatement decl) { + decl.accept_children (this); + } + + public override void visit_source_file (SourceFile source_file) { + source_file.accept_children (this); + } + + public override void visit_class (Class cl) { + cl.accept_children (this); + } + + public override void visit_struct (Struct st) { + st.accept_children (this); + } + + public override void visit_interface (Interface iface) { + iface.accept_children (this); + } + + public override void visit_enum (Enum en) { + en.accept_children (this); + } + + public override void visit_error_domain (ErrorDomain ed) { + ed.accept_children (this); + } + + public override void visit_method (Method m) { + m.accept_children (this); + } + + public override void visit_local_variable (LocalVariable local) { + if (local.initializer != null) { + local.initializer.accept (this); + } + } +}