crust_class.py 74.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
#!/usr/bin/env python3

# Copyright 2017 (C) Raster Software Vigo (Sergio Costas)
#
# This file is part of CRUST
#
# CRUST is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License.
#
# CRUST is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>

from ctypes import *
from crust import tokens
import sys
import subprocess
import gettext

import glob
import os
import pkg_resources
import copy
29
from crust.crust_helpers import crust_helpers
30 31 32 33

_ = gettext.gettext


34
class crust(crust_helpers):
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652

    def __init__(self, headerpath, libpath, do_print_debug = False):

        super().__init__(headerpath, libpath)

        self.expoints = {}


    def _eval_statement(self, statement, thread_status, first, is_conditional = False):
        """ Evaluates an statement recursively.
        @params:
            statement -- the node with the statement to evaluate
            thread_status -- the current status of the variables before evaluating the statement
            first -- whether this is the first node in an statement or not
        @return
            A list with as many dictionaries as possible bifurcations. Each dictionary contains the following elements:
                *"thread_status": the status of the thread and variables after the statement
                *"value" the statement value (self.VALUE_XXXXXXX)
                *"type" the type of the statement (self.TYPE_CRUST, self.TYPE_NO_CRUST or self.TYPE_NO_MATTER)
                *"condition" the boolean value (CONDITION_TRUE, CONDITION_FALSE or CONDITION_FALSE_TRUE) equivalent to that
                *"node" the variable node being assigned, or None if it is not a variable
                *"pure" will be FALSE if the node has been modified by '*' or '&'
        """

        if thread_status["find_label"] is not None:
            return [ {"thread_status":thread_status, "value":self.VALUE_NOT_NULL_OR_NULL, "type":self.TYPE_NO_CRUST, "condition":self.CONDITION_FALSE_TRUE, "node":None, "pure": True} ]

        if statement is None:
            return [ {"thread_status":thread_status, "value":crust.VALUE_UNINITIALIZED, "type":self.TYPE_NO_MATTER, "condition":self.CONDITION_FALSE_TRUE, "node":None, "pure": True} ]

        #print(statement.type)
        if statement.type == "CONSTANT":
            if (statement.t_null) or (statement.intval == 0):
                return [ {"thread_status":thread_status, "value":self.VALUE_NULL, "type":self.TYPE_NO_MATTER, "condition":self.CONDITION_FALSE, "node":None, "pure": True} ]
            else:
                return [ {"thread_status":thread_status, "value":self.VALUE_NOT_NULL, "type":self.TYPE_NO_CRUST, "condition":self.CONDITION_TRUE, "node":None, "pure": True} ]

        if statement.type == "SIZEOF":
            return [ {"thread_status":thread_status, "value":self.VALUE_NOT_NULL, "type":self.TYPE_NO_CRUST, "condition":self.CONDITION_TRUE, "node":None, "pure": True} ]

        if statement.type == "FCONSTANT":
            if statement.floatval == 0.0:
                return [ {"thread_status":thread_status, "value":self.VALUE_NULL, "type":self.TYPE_NO_MATTER, "condition":self.CONDITION_FALSE, "node":None, "pure": True} ]
            else:
                return [ {"thread_status":thread_status, "value":self.VALUE_NOT_NULL, "type":self.TYPE_NO_CRUST, "condition":self.CONDITION_TRUE, "node":None, "pure": True} ]


        if statement.type == "STRING_LITERAL":
            return [ {"thread_status":thread_status, "value":self.VALUE_NOT_NULL, "type":self.TYPE_NO_CRUST, "condition":self.CONDITION_TRUE, "node":None, "pure": True} ]


        if statement.type == "IDENTIFIER":
            vardata = self._find_variable(thread_status, statement.name, statement.line)

            if statement.right is not None:
                valuetype = self.TYPE_NO_MATTER
                valuedata = self.VALUE_NOT_NULL_OR_NULL
                pure = False
            else:
                valuedata = vardata["value"]
                if self._check_statement_is_crust(statement, vardata):
                    valuetype = self.TYPE_CRUST
                    pure = True
                else:
                    valuetype = self.TYPE_NO_CRUST
                    pure = True
                if is_conditional:
                    self._check_can_be_used(thread_status, statement, statement.line, True)
                if (valuedata == self.VALUE_NOT_NULL_OR_NULL) and is_conditional:
                    var1 = self._copy_status(thread_status)
                    var2 = thread_status
                    self._set_var_value(var1, vardata["name"], crust.VALUE_NOT_NULL, 0, True)
                    self._set_var_value(var2, vardata["name"], crust.VALUE_NULL, 0, True)
                    return [ {"thread_status":var1, "value":self.VALUE_NOT_NULL, "type":valuetype, "condition":self.CONDITION_TRUE, "node":statement, "pure": True}, {"thread_status":var2, "value":self.VALUE_NULL, "type":valuetype, "condition":self.CONDITION_FALSE, "node":statement, "pure": True}]
                if (valuedata == self.VALUE_FREED_OR_NULL) and is_conditional:
                    var1 = self._copy_status(thread_status)
                    var2 = thread_status
                    self._set_var_value(var1, vardata["name"], crust.VALUE_FREED, 0, True)
                    self._set_var_value(var2, vardata["name"], crust.VALUE_NULL, 0, True)
                    return [ {"thread_status":var1, "value":self.VALUE_FREED, "type":valuetype, "condition":self.CONDITION_TRUE, "node":statement, "pure": True}, {"thread_status":var2, "value":self.VALUE_NULL, "type":valuetype, "condition":self.CONDITION_FALSE, "node":statement, "pure": True}]
            return [ {"thread_status":thread_status, "value":valuedata, "type":valuetype, "condition":self._status_to_condition(valuedata), "node":statement, "pure": pure} ]

        if statement.type == "TYPECAST":

            threads = self._eval_statement(statement.child1[0], thread_status, False)
            if self._check_statement_is_crust(statement):
                valuetype = self.TYPE_CRUST
            else:
                valuetype = self.TYPE_NO_CRUST
            for val in threads:
                val["type"] = valuetype
            return threads


        if statement.type == "?":

            threads = self._eval_statement(statement.condition[0], thread_status, False, True)
            retvals = []
            for thread in threads:
                if thread["condition"] == crust.CONDITION_FALSE_TRUE:
                    retvals += self._eval_statement(statement.child1[0], self._copy_status(thread["thread_status"]), False)
                    retvals += self._eval_statement(statement.child2[0], thread["thread_status"], False)
                elif thread["condition"] == crust.CONDITION_TRUE:
                    retvals += self._eval_statement(statement.child1[0], thread["thread_status"], False)
                else: # thread["condition"] == crust.CONDITION_FALSE:
                    retvals += self._eval_statement(statement.child2[0], thread["thread_status"], False)
            return retvals


        if statement.type == "=":

            if (statement.child1[0].t_struct and (not statement.child1[0].right) and (statement.child1[0].pointer == 0)):
                # static initialization of this struct
                threads = [{ "thread_status": thread_status, "value": self.VALUE_NOT_NULL, "type": self.TYPE_NO_MATTER, "condition":self.CONDITION_TRUE, "node": statement.child1[0], "pure": True}]
            else:
                threads = self._eval_statement(statement.child2[0], self._copy_status(thread_status), False) # evaluate the right part of the assignment

            dest_eval = self._eval_statement(statement.child1[0], thread_status, False)[0]
            dest_var = dest_eval["node"]
            if dest_var is None:
                dest_data = None
                dest_type = self.TYPE_NO_MATTER
            else:
                dest_data = self._find_variable(thread_status, dest_var.name, statement.line)

                if dest_var.right is not None:
                    dest_type = self.TYPE_NO_MATTER
                else:
                    if dest_data["crust"] and (dest_data["function_params"] is None):
                        dest_type = self.TYPE_CRUST
                    else:
                        dest_type = self.TYPE_NO_CRUST

            if (dest_data is not None) and dest_data["borrowed"] and (dest_var.right is None) and dest_data["is_parameter"]:
                self._add_error(thread_status, self.MSG_ERROR, "Overwritting the borrowed argument '{:s}' at line {:d}", dest_data["name"], statement.line)

            retvals = []
            for orig_eval in threads:
                if dest_var is None:
                    dest_data = None
                else:
                    dest_data = self._find_variable(orig_eval["thread_status"], dest_var.name, statement.line)
                orig_type = orig_eval["type"]
                orig_var = orig_eval["node"]
                if orig_var is not None:
                    orig_data = self._find_variable(orig_eval["thread_status"], orig_var.name, orig_var.line)
                    if orig_data["function"]:
                        if orig_var.function_params is None:
                            # we are assigning a function pointer
                            # here we should compare the type of the function pointer being assigned
                            # and the function pointer variable where is being stored

                            # if we are assigning to an struct element, don't compare parameters
                            if not dest_var.right:
                                # first, check if any of the parameters or return values in both pointers are crust-type
                                crust_found = False
                                for param in orig_data["function_params"]:
                                    if param["crust"]:
                                        crust_found = True
                                if dest_data["function_params"] is None:
                                    self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Trying to assign the function '{:s}' into the variable '{:s}', which is not a pointer to function, at line {:d}", orig_data["name"], dest_data["name"], statement.line)
                                    crust_found = False
                                else:
                                    for param in dest_data["function_params"]:
                                        if param["crust"]:
                                            crust_found = True
                                if crust_found:
                                    # we only check if the param types and number are fine if there are crust-type parameters involved
                                    if len(orig_data["function_params"]) != len(dest_data["function_params"]):
                                        self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Trying to assign the function '{:s}' to the function pointer '{:s}' at line {:d}, but they have a different number of arguments", orig_data["name"], dest_data["name"], statement.line)
                                    else:
                                        for c in range(len(orig_data["function_params"])):
                                            if not self._compare_types(orig_data["function_params"][c], dest_data["function_params"][c]):
                                                if c == 0:
                                                    self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Trying to assign the function '{:s}' to the function pointer '{:s}' at line {:d}, but the return types differ", orig_data["name"], dest_data["name"], statement.line)
                                                else:
                                                    self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Trying to assign the function '{:s}' to the function pointer '{:s}' at line {:d}, but argument {:d} differs", orig_data["name"], dest_data["name"], statement.line, c)
                            if dest_data["alias"]:
                                self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Assigning a pointer of the function '{:s}' to the alias '{:s}' at line {:d}", orig_data["name"], dest_data["name"], statement.line)
                        else:
                            # we are assigning the result of a function call
                            ret_value = orig_data["function_params"][0]
                            if ret_value["crust"] and (dest_type == self.TYPE_NO_CRUST):
                                if (orig_eval["type"] == self.TYPE_CRUST):
                                    self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Assigning the crust-type result value of function '{:s}' at line {:d} to the non-crust variable '{:s}'", str(orig_data["name"]), statement.line, dest_data["name"])
                                else:
                                    self._add_error(orig_eval["thread_status"], self.MSG_WARNING, "Assigning, with a typecast, the crust-type result value of function '{:s}' at line {:d} to the non-crust variable '{:s}'", str(orig_data["name"]), statement.line, dest_data["name"])
                            if (not ret_value["crust"]) and (dest_type == self.TYPE_CRUST):
                                if (orig_eval["type"] == self.TYPE_NO_CRUST):
                                    self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Assigning the non-crust-type result value of function '{:s}' at line {:d} to the crust variable '{:s}'", orig_data["name"], statement.line, dest_data["name"])
                                else:
                                    if (orig_data["name"] != "malloc") and (orig_data["name"] != "calloc") and (orig_data["name"] != "realloc"):
                                        self._add_error(orig_eval["thread_status"], self.MSG_WARNING, "Assigning, with a typecast, the non-crust-type result value of function '{:s}' at line {:d} to the crust variable '{:s}'", orig_data["name"], statement.line, dest_data["name"])
                            if ret_value["borrowed"] and (not dest_data["borrowed"]):
                                self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Assigning the borrowed result value of function '{:s}' to the non-borrowed variable '{:s}' at line {:d}", orig_data["name"], dest_data["name"], statement.line)
                            if dest_data["borrowed"] and (not ret_value["borrowed"]):
                                self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Assigning the non-borrowed result value of function '{:s}' to the borrowed variable '{:s}' at line {:d}", orig_data["name"], dest_data["name"], statement.line)
                            if dest_data["alias"]:
                                self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Assigning the crust-type result value of function '{:s}' to the alias '{:s}' at line {:d}", orig_data["name"], dest_data["name"], statement.line)
                    else:
                        if (dest_data is not None) and dest_data["borrowed"] and (dest_var.right is None) and dest_eval["pure"] and ((not orig_data["borrowed"]) or (orig_var.right is not None)):
                            self._add_error(thread_status, self.MSG_ERROR, "Assigning the non-borrowed block '{:s}' to the borrowed variable '{:s}' at line {:d}", orig_data["name"], dest_data["name"], statement.line)
                        if orig_data["borrowed"] and (orig_var.right is None):
                            if ((dest_data is not None) and ((not dest_data["borrowed"]) or (dest_var.right is not None))) or (not dest_eval["pure"]):
                                self._add_error(orig_eval["thread_status"], self.MSG_CRITICAL, "Assigning a borrowed block into a local variable is not allowed (assigning '{:s}' at line {:d})", orig_data["name"], statement.line)
                        if (orig_type == self.TYPE_CRUST) and (dest_type == self.TYPE_NO_CRUST):
                            self._add_error(orig_eval["thread_status"], self.MSG_CRITICAL, "Assigning the crust variable '{:s}' to the non-crust variable '{:s}' at line {:d}", orig_var.name, dest_data["name"], statement.line)
                        if (orig_type == self.TYPE_NO_CRUST) and (dest_type == self.TYPE_CRUST):
                            self._add_error(orig_eval["thread_status"], self.MSG_CRITICAL, "Assigning the non-crust variable '{:s}' to the crust variable '{:s}' at line {:d}", orig_var.name, dest_data["name"], statement.line)
                else:
                    orig_data = None
                    if (orig_type == self.TYPE_CRUST) and (dest_type == self.TYPE_NO_CRUST):
                        self._add_error(orig_eval["thread_status"], self.MSG_CRITICAL, "Assigning a crust pointer to the non-crust variable '{:s}' at line {:d}", dest_data["name"], statement.line)
                    if (orig_type == self.TYPE_NO_CRUST) and (dest_type == self.TYPE_CRUST) and (orig_eval["value"] != self.VALUE_NULL):
                        self._add_error(orig_eval["thread_status"], self.MSG_CRITICAL, "Assigning a non-crust value to the crust variable '{:s}' at line {:d}", dest_data["name"], statement.line)
                    if (dest_var is not None) and (dest_data is not None) and dest_data["borrowed"] and (dest_var.right is None) and (orig_eval["value"] != self.VALUE_NULL) and dest_eval["pure"]:
                            self._add_error(thread_status, self.MSG_ERROR, "Assigning a value to the borrowed variable '{:s}' at line {:d}", dest_data["name"], statement.line)

                if ((dest_var is not None) and (dest_var.right is not None) and (dest_data["pointer"] != 0)) or (not dest_eval["pure"]):
                    if dest_data["value"] == self.VALUE_NULL:
                        self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Using variable '{:s}' at line {:d} with a NULL value", dest_data["name"], statement.line)
                    elif (dest_data["value"] == self.VALUE_NOT_NULL_OR_NULL) and (dest_data["crust"]) and (not dest_data["global"]):
                        self._add_error(orig_eval["thread_status"], self.MSG_WARNING, "Using variable '{:s}' at line {:d} with a possible NULL value", dest_data["name"], statement.line)
                    elif dest_data["value"] == self.VALUE_UNINITIALIZED:
                        if (dest_var.right is None) or (dest_var.right[0].type != "."):
                            self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Using variable '{:s}' at line {:d}, but it hasn't been initialized yet", dest_data["name"], statement.line)
                    elif (dest_data["value"] == self.VALUE_FREED) or (dest_data["value"] == self.VALUE_FREED_OR_NULL):
                        self._add_error(orig_eval["thread_status"], self.MSG_ERROR, "Using variable '{:s}' at line {:d}, but it has been already freed", dest_data["name"], statement.line)

                old_block = None
                if (orig_type == self.TYPE_CRUST) and self._check_statement_is_crust(orig_var, orig_data):
                    old_block = orig_data["uid"]
                    #if old_block is None:
                    #    print("Es none {:d} {:s}".format(statement.line, str(orig_data)))
                    # only check and modify if the origin variable IS really a crust one, and is being managed as a crust one
                    self._check_can_be_used(orig_eval["thread_status"], orig_var, statement.line, False)
                    if (orig_eval["pure"] and (not dest_data["alias"]) and (not dest_data["global"])) or (dest_data["alias"] and (not dest_eval["pure"])) or (dest_data["global"] and (not dest_eval["pure"])):
                        self._free_block(orig_eval["thread_status"], orig_var.name, statement.line)

                if (old_block is None) and self._check_statement_is_crust(dest_var, dest_data) and ((orig_eval["value"] == self.VALUE_NOT_NULL) or (orig_eval["value"] == self.VALUE_NOT_NULL_OR_NULL)):
                    # assign a new block uid if needed
                    old_block = self.id_counter
                    self.id_counter += 1

                if (dest_var is not None) and (dest_var.right is None) and dest_eval["pure"]:
                    self._set_var_value(orig_eval["thread_status"], dest_var.name, orig_eval["value"], statement.line)
                    dest_data["uid"] = old_block

                retvals.append( { "thread_status":orig_eval["thread_status"], "value":orig_eval["value"], "type":dest_type, "condition":self._status_to_condition(orig_eval["value"]), "node":dest_var, "pure": dest_eval["pure"]} )
            return retvals


        if statement.type == "FUNCTION_CALL":

            function_data = self._find_function(thread_status, statement.name, statement.line)
            function_data_base = self._find_variable(thread_status, statement.name, statement.line)
            function_status = function_data_base["value"]
            has_ellipsis = function_data_base["ellipsis"]

            if (function_data is None):
                retval_type = self.TYPE_NO_MATTER
                retval_value = self.VALUE_NOT_NULL_OR_NULL
            else:
                if function_data[0]["not_null"]:
                    retval_value = self.VALUE_NOT_NULL
                else:
                    retval_value = self.VALUE_NOT_NULL_OR_NULL
                if function_data[0]["crust"]:
                    retval_type = self.TYPE_CRUST
                    retval_is_crust = True
                else:
                    retval_type = self.TYPE_NO_CRUST
                    retval_is_crust = False

            if function_status == self.VALUE_NULL:
                self._add_error(thread_status, self.MSG_ERROR, "Using function pointer '{:s}' at line {:d} with NULL value", str(statement.name), statement.line)
            elif function_status == self.VALUE_NOT_NULL_OR_NULL:
                if statement.right:
                    self._add_error(thread_status, self.MSG_WARNING, "Using variable '{:s}' at line {:d} with a possible NULL value", str(statement.name), statement.line)
                else:
                    self._add_error(thread_status, self.MSG_WARNING, "Using function pointer '{:s}' at line {:d} with a possible NULL value", str(statement.name), statement.line)
            elif function_status == self.VALUE_UNINITIALIZED:
                self._add_error(thread_status, self.MSG_ERROR, "Using uninitialized function pointer '{:s}' at line {:d}", str(statement.name), statement.line)

            if ((function_data is not None) and (((not has_ellipsis) and (len(function_data) - 1) != len(statement.function_params)) or
                (has_ellipsis and (len(function_data) - 1) > len(statement.function_params)))):
                self._add_error(thread_status, self.MSG_CRITICAL, "Calling function '{:s}' at line {:d} with an incorrect number of arguments", statement.name, statement.line)
                return [ {"thread_status":thread_status, "value":retval_value, "type":retval_type, "condition":self.CONDITION_FALSE_TRUE, "node":statement, "pure": True} ]

            if (retval_type == self.TYPE_CRUST) and first and ((function_data is None) or (not function_data[0]["borrowed"])):
                self._add_error(thread_status, self.MSG_ERROR, "Calling the function '{:s}' at line {:d}, but ignoring the crust-type return value", statement.name, statement.line)

            # stores the execution branches and the return value for each one, in case there is a recycle argument
            retvals = [ {"thread_status":thread_status, "value":retval_value, "type": retval_type, "condition":self.CONDITION_FALSE_TRUE, "node":statement, "pure": True} ]
            error_found = False

            if function_data is None:
                lenfunction_data = 0
            else:
                lenfunction_data = len(function_data) - 1
            if statement.function_params is not None:
                for pos in range(len(statement.function_params)):
                    if pos < lenfunction_data:
                        param_is_crust = function_data[pos+1]["crust"]
                        param_is_borrowed = function_data[pos+1]["borrowed"]
                        param_is_recycled = function_data[pos+1]["recycle"]
                        param_is_not_null = function_data[pos+1]["not_null"]
                        variable_parameters = False
                    else:
                        param_is_crust = False
                        param_is_borrowed = False
                        param_is_recycled = False
                        param_is_not_null = False
                        variable_parameters = True

                    variable = statement.function_params[pos]
                    tmp_retvals = []
                    # Evaluate each argument in each possible execution branch
                    nparam = -1
                    for rval in retvals:
                        nparam += 1
                        threads = self._eval_statement(variable, rval["thread_status"], False)
                        for thread in threads:
                            # Update the state of the variables
                            passed_is_crust = (thread["type"] == self.TYPE_CRUST)
                            if thread["node"] is None:
                                var_data = None
                                # prepare the execution branches for the next variable
                                tmp_retvals.append( {"thread_status":thread["thread_status"], "value":rval["value"], "type": retval_type, "condition":self._status_to_condition(rval["value"]), "node":statement, "pure": True} )
                            else:
                                var_data = self._find_variable(thread["thread_status"], thread["node"].name, thread["node"].line)
                                # prepare the execution branches for the next variable
                                if param_is_recycled and (var_data["value"] == self.VALUE_NOT_NULL):
                                    tmp_retvals.append( {"thread_status":thread["thread_status"], "value":self.VALUE_NOT_NULL, "type": retval_type, "condition":self.CONDITION_TRUE, "node":statement, "pure": True} )
                                else:
                                    tmp_retvals.append( {"thread_status":thread["thread_status"], "value":rval["value"], "type": retval_type, "condition":self._status_to_condition(rval["value"]), "node":statement, "pure": True} )

                            if (thread["value"] == self.VALUE_NULL) and (var_data is None):
                                # passing "NULL" as-is as param
                                if param_is_not_null:
                                    self._add_error(thread["thread_status"], self.MSG_ERROR, "Passing NULL as argument {:d} when calling function '{:s}' at line {:d}, but it must be a not_null value", pos+1, statement.name, statement.line)
                                continue

                            if param_is_crust:
                                if var_data is None:
                                    self._add_error(thread["thread_status"], self.MSG_CRITICAL, "Expected a __crust__ variable as argument {:d} when calling function '{:s}' at line {:d}", pos+1, statement.name, statement.line)
                                    error_found = True
                                    continue
                                if not passed_is_crust:
                                    self._add_error(thread["thread_status"], self.MSG_CRITICAL, "Expected a __crust__ variable as argument {:d} when calling function '{:s}' at line {:d}, but passed non __crust__ variable", pos+1, statement.name, statement.line)
                                    error_found = True
                                    continue
                            else:
                                if passed_is_crust and (statement.name != "free") and (statement.name != "realloc"):
                                    if variable_parameters:
                                        self._add_error(thread["thread_status"], self.MSG_CRITICAL, "Passed a __crust__ variable as argument {:d} when calling function '{:s}' at line {:d}, but __crust__ variables are not allowed for optional arguments in functions with variable number of arguments", pos+1, statement.name, statement.line)
                                    else:
                                        self._add_error(thread["thread_status"], self.MSG_CRITICAL, "Expected a non __crust__ variable as argument {:d} when calling function '{:s}' at line {:d}, but passed a __crust__ variable", pos+1, statement.name, statement.line)
                                    error_found = True
                                    continue

                            if (statement.name != "free") and (statement.name != "realloc"):
                                if (not param_is_crust):
                                    continue

                            if statement.name == "realloc":
                                if (nparam == 0) and (not passed_is_crust):
                                    continue

                            if var_data["value"] == self.VALUE_UNINITIALIZED:
                                self._add_error(thread["thread_status"], self.MSG_ERROR, "Argument {:d} when calling function '{:s}' at line {:d} isn't initialized", pos+1, statement.name, statement.line)
                                error_found = True
                                continue
                            if (var_data["value"] == self.VALUE_FREED) or (var_data["value"] == self.VALUE_FREED_OR_NULL):
                                self._add_error(thread["thread_status"], self.MSG_ERROR, "Argument {:d} when calling function '{:s}' at line {:d} was freed at line {:d}", pos+1, statement.name, statement.line, var_data["init_line"])
                                error_found = True
                                continue

                            if param_is_not_null:
                                if (var_data["value"] == self.VALUE_NULL):
                                    self._add_error(thread["thread_status"], self.MSG_ERROR, "Argument '{:s}' at position {:d} when calling function '{:s}' at line {:d} is defined as not_null, but is being called with a NULL value", thread["node"].name, pos+1, statement.name, statement.line)
                                if (var_data["value"] == self.VALUE_NOT_NULL_OR_NULL) and (not var_data["global"]):
                                    self._add_error(thread["thread_status"], self.MSG_WARNING, "Argument '{:s}' at position {:d} when calling function '{:s}' at line {:d} is defined as not_null, but is being called with a possible NULL value", thread["node"].name, pos+1, statement.name, statement.line)

                            if (not param_is_borrowed) and passed_is_crust and self._check_statement_is_crust(thread["node"], var_data):
                                self._free_block(thread["thread_status"], variable.name, statement.line)
                                if var_data["borrowed"]:
                                    self._add_error(thread["thread_status"], self.MSG_CRITICAL, "Argument '{:s}' at position {:d} when calling function '{:s}' at line {:d} is borrowed, but is used as a non-borrow argument", thread["node"].name, pos+1, statement.name, statement.line)
                    retvals = tmp_retvals

            if error_found:
                return [ {"thread_status":thread_status, "value":retval_value, "type":retval_type, "condition":self.CONDITION_FALSE_TRUE, "node":statement, "pure": True} ]

            return retvals

        if (statement.type == "EQ_OP") or (statement.type == "NE_OP"):
            retvals = []
            threads1 = self._eval_statement(statement.child1[0], thread_status, False, True)
            if statement.type == "EQ_OP":
                sts1 = self.VALUE_NOT_NULL
                cnd1 = self.CONDITION_TRUE
                sts2 = self.VALUE_NULL
                cnd2 = self.CONDITION_FALSE
            else:
                sts2 = self.VALUE_NOT_NULL
                cnd2 = self.CONDITION_TRUE
                sts1 = self.VALUE_NULL
                cnd1 = self.CONDITION_FALSE
            for thread1 in threads1:
                threads2 = self._eval_statement(statement.child2[0], thread1["thread_status"], False, True)
                var_used = False
                for thread2 in threads2:
                    if var_used:
                        variables = self._copy_status(thread2["thread_status"])
                    else:
                        variables = thread2["thread_status"]
                        var_used = True
                    if (thread1["condition"] == self.CONDITION_FALSE) and (thread2["condition"] == self.CONDITION_FALSE):
                        retvals.append( {"thread_status":variables, "value":sts1, "type":self.TYPE_NO_CRUST, "condition":cnd1, "node":None, "pure": True} )
                    elif (((thread1["condition"] == self.CONDITION_FALSE) and (thread2["condition"] == self.CONDITION_TRUE)) or
                        ((thread1["value"] == self.VALUE_NOT_NULL) and (thread2["value"] == self.VALUE_NULL))):
                        retvals.append( {"thread_status":variables, "value":sts2, "type":self.TYPE_NO_CRUST, "condition":cnd2, "node":None, "pure": True} )
                    else:
                        retvals.append( {"thread_status":variables, "value":self.VALUE_NOT_NULL_OR_NULL, "type":self.TYPE_NO_CRUST, "condition":self.CONDITION_FALSE_TRUE, "node":None, "pure": True} )
            return retvals

        if (statement.type == "!") or (statement.type == "~"):
            threads = self._eval_statement(statement.child1[0], thread_status, False, True)
            for thread in threads:
                if thread["value"] == self.VALUE_NULL:
                    thread["value"] = self.VALUE_NOT_NULL
                elif thread["value"] == self.VALUE_NOT_NULL:
                    thread["value"] = self.VALUE_NULL
                if thread["condition"] == self.CONDITION_FALSE:
                    thread["condition"] = self.CONDITION_TRUE
                elif thread["condition"] == self.CONDITION_TRUE:
                    thread["condition"] = self.CONDITION_FALSE
                thread["type"] = self.TYPE_NO_CRUST
            return threads


        if (statement.type == "&") and (statement.child2 is None): # &variable
            threads = self._eval_statement(statement.child1[0], thread_status, False)
            for thread in threads:
                thread["type"] = self.TYPE_NO_CRUST
                thread["pure"] = False
            return threads


        if (statement.type == "*") and (statement.child2 is None): # *variable
            threads = self._eval_statement(statement.child1[0], thread_status, False)
            for thread in threads:
                thread["type"] = self.TYPE_NO_CRUST
                thread["pure"] = False
            return threads


        if (((statement.type == "+") or
            (statement.type == "-")) and
            (statement.child2 is None)):
            return self._eval_statement(statement.child1[0], thread_status, first)

        if ((statement.type == "INC_OP") or
            (statement.type == "DEC_OP")):
            if statement.child1 is None:
                child = statement.child2
            else:
                child = statement.child1
            vardata = self._find_variable(thread_status, child[0].name, statement.line)
            if (self._check_statement_is_crust(child[0], vardata)):
                if statement.type == "INC_OP":
                    self._add_error(thread_status, self.MSG_CRITICAL, "Using autoincrement with __crust__ pointer '{:s}' at line {:d}", child[0].name, statement.line)
                else:
                    self._add_error(thread_status, self.MSG_CRITICAL, "Using autodecrement with __crust__ pointer '{:s}' at line {:d}", child[0].name, statement.line)
                return self._eval_statement(child[0], thread_status, first)

            tmp1 = statement.copy()
            if tmp1.child1 is None:
                tmp1.child1 = tmp1.child2
                tmp1.child2 = None
            tmp1.type = "="
            tmp2 = statement.copy()
            if statement.type == "INC_OP":
                tmp2.type = "+"
            elif statement.type == "DEC_OP":
                tmp2.type = "-"
            if tmp2.child1 is None:
                tmp2.child1 = tmp2.child2
            tmp3 = statement.copy()
            tmp3.type = "CONSTANT"
            tmp3.t_null = False
            tmp3.intval = 1
            tmp1.child2 = [ tmp2 ] # child1 is the statement to be incremented
            tmp2.child2 = [ tmp3 ] # child1 is the statement to be incremented
            return self._eval_statement(tmp1, thread_status, first)

        if ((statement.type == "MUL_ASSIGN") or
            (statement.type == "DIV_ASSIGN") or
            (statement.type == "ADD_ASSIGN") or
            (statement.type == "SUB_ASSIGN") or
            (statement.type == "MOD_ASSIGN") or
            (statement.type == "AND_ASSIGN") or
            (statement.type == "XOR_ASSIGN") or
            (statement.type == "OR_ASSIGN") or
            (statement.type == "LEFT_ASSIGN") or
            (statement.type == "RIGHT_ASSIGN")):

            tmp1 = statement.copy()
            tmp1.type = "="
            tmp2 = statement.copy()
            if statement.type == "MUL_ASSIGN":
                tmp2.type = "*"
            elif statement.type == "DIV_ASSIGN":
                tmp2.type = "/"
            elif statement.type == "ADD_ASSIGN":
                tmp2.type = "+"
            elif statement.type == "SUB_ASSIGN":
                tmp2.type = "-"
            elif statement.type == "MOD_ASSIGN":
                tmp2.type = "%"
            elif statement.type == "AND_ASSIGN":
                tmp2.type = "&"
            elif statement.type == "XOR_ASSIGN":
                tmp2.type = "^"
            elif statement.type == "OR_ASSIGN":
                tmp2.type = "|"
            elif statement.type == "LEFT_ASSIGN":
                tmp2.type = "LEFT_OP"
            elif statement.type == "RIGHT_ASSIGN":
                tmp2.type = "RIGHT_OP"
            tmp1.child2 = [ tmp2 ]
            return self._eval_statement(tmp1, thread_status, first)


        if ((statement.type == "GE_OP") or
            (statement.type == "LE_OP") or
            (statement.type == "<") or
            (statement.type == ">") or
            (statement.type == "|") or
            (statement.type == "+") or
            (statement.type == "-") or
            (statement.type == "*") or
            (statement.type == "/") or
            (statement.type == "&") or
            (statement.type == "^") or
            (statement.type == "%") or
            (statement.type == "RIGHT_OP") or
            (statement.type == "LEFT_OP")):
            child1 = statement.child1
            child2 = statement.child2
            retvals = []
            if ((statement.type == "GE_OP") or
            (statement.type == "LE_OP") or
            (statement.type == "<") or
            (statement.type == ">")):
                is_comparison = True
            else:
                is_comparison = False
            threads1 = self._eval_statement(statement.child1[0], thread_status, False, is_comparison)
            for thread1 in threads1:
                threads2 = self._eval_statement(statement.child2[0], thread1["thread_status"], False, is_comparison)
                for thread2 in threads2:
                    if ((thread1["value"] == self.VALUE_FREED) or
                        (thread2["value"] == self.VALUE_FREED) or
                        (thread1["value"] == self.VALUE_FREED_OR_NULL) or
                        (thread2["value"] == self.VALUE_FREED_OR_NULL) or
                        (thread1["value"] == self.VALUE_UNINITIALIZED) or
                        (thread2["value"] == self.VALUE_UNINITIALIZED)):
                        new_status = self.VALUE_UNINITIALIZED
                    else:
                        new_status = self.VALUE_NOT_NULL_OR_NULL
                    retvals += [ {"thread_status":thread2["thread_status"], "value":new_status, "type":self.TYPE_NO_CRUST, "condition":crust.CONDITION_FALSE_TRUE, "node":None, "pure": True} ]
            return retvals

        if statement.type == "AND_OP":
            threads = self._eval_statement(statement.child1[0], thread_status, False, True)
            retval = []
            for thread in threads:
                if (thread["condition"] == self.CONDITION_FALSE) or (thread["condition"] == self.CONDITION_FALSE_TRUE):
                    retval.append(thread)
                if (thread["condition"] == self.CONDITION_TRUE) or (thread["condition"] == self.CONDITION_FALSE_TRUE):
                    retval += self._eval_statement(statement.child2[0], self._copy_status(thread["thread_status"]), False, True)
            return retval

        if statement.type == "OR_OP":
            threads = self._eval_statement(statement.child1[0], thread_status, False, True)
            retval = []
            for thread in threads:
                if (thread["condition"] == self.CONDITION_TRUE) or (thread["condition"] == self.CONDITION_FALSE_TRUE):
                    retval.append(thread)
                if (thread["condition"] == self.CONDITION_FALSE) or (thread["condition"] == self.CONDITION_FALSE_TRUE):
                    retval += self._eval_statement(statement.child2[0], self._copy_status(thread["thread_status"]), False, True)
            return retval

        if statement.type == ",":
            threads = self._eval_statement(statement.child1[0], thread_status, True)
            retval = []
            for thread in threads:
                retval += self._eval_statement(statement.child2[0], thread["thread_status"], False)
            return retval

        print("statement type unknown '{:s}' at line {:d}, file {:s}".format(statement.type, statement.line, statement.filename))
        return [ {"thread_status":thread_status, "value":self.VALUE_NOT_NULL_OR_NULL, "type":self.TYPE_NO_CRUST, "condition":self.CONDITION_FALSE_TRUE, "node":None, "pure": True} ]


    def process_tree(self, tree, filename, verbose = False):
        """ Process the main tree with the global variables and the function definitions and declarations """

        self.verbose = verbose
        thread_status = { "variables": [ {} ], "debug_level": 0, "loop_level": 0, "find_label": None, "return_is_crust": None, "return_is_borrowed": False, "return_cant_null": False, "returned_something": None, "current_function": "", "filename": filename }

        # Set the GCC builtin functions

        self._add_builtin_function(thread_status, "__builtin_bswap16", "uint16_t", ["uint16_t"])
        self._add_builtin_function(thread_status, "__builtin_bswap32", "uint32_t", ["uint32_t"])
        self._add_builtin_function(thread_status, "__builtin_bswap64", "uint64_t", ["uint64_t"])
        self._add_builtin_function(thread_status, "__builtin_clz", "int", ["unsigned int"])

653
        function_list = []
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685
        # First fill all the global variables and the function declarations
        for node in tree:
            if (node.type == "TYPE_SPECIFIER"):
                if (node.enum_data is not None):
                    self._process_enum(node, thread_status)
                continue
            if node.type == "EMPTY_DECLARATOR":
                continue
            if node.type == "VARIABLE_DEFINITION":
                if (node.enum_data is not None):
                    self._process_enum(node, thread_status)
                if not node.t_typedef:
                    pointers = node.pointer
                    if node.arrays is not None:
                        for element in node.arrays:
                            pointers += 1
                    tmpparams = self._get_variable_properties(thread_status, node, pointers, False)
                    if tmpparams["borrowed"]:
                        self._add_error(thread_status, self.MSG_CRITICAL, "Global variable '{:s}', defined as __crust_borrow__ in line {:d}, but that is not allowed", node.name, node.line)
                    tmpparams["name"] = node.name
                    tmpparams["global"] = True
                    #tmpparams = self._node_fill_parameters(thread_status, node, tmpparams)
                    thread_status["variables"][0][node.name] = tmpparams
                continue
            if (node.type == "FUNCTION_DECLARATION") or (node.type == "FUNCTION_DEFINITION"):
                counter = 0
                if node.function_params is not None:
                    for parameter in node.function_params:
                        if parameter.t_void and (parameter.pointer == 0) and not parameter.function:
                            continue
                        if parameter.t_crust_recycle:
                            counter += 1
686 687 688
                if (node.type == "FUNCTION_DEFINITION") and (node.filename == filename):
                    # add only the functions defined in this file, to check their declarations in other files
                    function_list.append(node.name)
689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
                if counter > 1:
                    self._add_error(thread_status, self.MSG_CRITICAL, "The function '{:s}', at line {:d}, has more than one RECYCLE argument", node.name, node.line)
                if (counter != 0) and (not self._check_statement_is_crust(node.return_value[0])):
                    self._add_error(thread_status, self.MSG_CRITICAL, "The function '{:s}', at line {:d}, has a RECYCLE argument, but doesn't return a CRUST pointer", node.name, node.line)
                if not node.t_typedef:
                    # the first argument is the return value
                    parameters = [ self._get_variable_properties(thread_status, node.return_value[0], node.return_value[0].pointer, False, node.name) ]
                    if node.function_params is not None:
                        for parameter in node.function_params:
                            if parameter.t_void and (parameter.pointer == 0) and not parameter.function:
                                continue
                            parameters.append( self._get_variable_properties(thread_status, parameter, parameter.pointer, True) )
                    # also add functions as variables to allow to assign them to function pointers
                    tmpparams = self._get_variable_properties(thread_status, None, node.pointer, False)
                    tmpparams["function"] = True
                    tmpparams["value"] = crust.VALUE_FUNCTION
                    tmpparams["function_params"] = parameters
                    tmpparams["name"] = node.name
                    tmpparams["ellipsis"] = node.t_ellipsis
                    tmpparams["definition_line"] = node.line
                    tmpparams["definition_file"] = node.filename
                    tmpparams["override"] = node.t_override
                    if node.name in thread_status["variables"][0]:
                        function_data = thread_status["variables"][0][node.name]
                        if tmpparams["override"]:
                            del thread_status["variables"][0][node.name]
                            thread_status["variables"][0][node.name] = tmpparams
                        else:
                            if not function_data["override"]:
                                differ = False
                                if function_data["ellipsis"] != tmpparams["ellipsis"]:
                                    differ = True
                                elif len(function_data["function_params"]) != len(tmpparams["function_params"]):
                                    differ = True
                                else:
                                    for c in range(len(function_data["function_params"])):
                                        if not self._compare_types(function_data["function_params"][c], tmpparams["function_params"][c], True):
                                            differ = True
                                            break
                                if differ:
                                    self._add_error(thread_status, self.MSG_CRITICAL, "Function definition for '{:s}' at line {:d}, file '{:s}' differs from definition at line {:d}, file '{:s}'", node.name, node.line, node.filename, function_data["definition_line"], function_data["definition_file"])
                    else:
                        thread_status["variables"][0][node.name] = tmpparams
                continue
            if node.type == "CRUST_DEBUG":
                self._do_debug(thread_status)
                continue
736 737 738 739 740 741 742 743 744 745 746
        # Check for functions that have no parameters
        for node in tree:
            if (node.type != "FUNCTION_DECLARATION") and (node.type != "FUNCTION_DEFINITION"):
                continue
            if (node.name in function_list) or (node.filename == filename):
                # if a definition or declaration is in this file, or if the declaration is in another file but the definition is here, check if it has no parameters
                if (node.function_params is None) or (len(node.function_params) == 0):
                    if node.filename == filename:
                        self._add_error({"debug_level": 0}, self.MSG_ERROR, "Function '{:s}' at line {:d} has no parameters (must have 'void' inside the parentheses)", node.name, node.line)
                    else:
                        self._add_error({"debug_level": 0}, self.MSG_ERROR, "Function '{:s}' at line {:d} in file '{:s}' has no parameters (must have 'void' inside the parentheses)", node.name, node.line, node.filename)
747 748 749 750
        return thread_status


    def process_functions(self, tree, thread_status):
Sergio Costas's avatar
Sergio Costas committed
751 752 753

        """ Takes each function in the tree and processes the code inside them """

754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806
        last_len = 0
        total_len = 0
        for node in tree:
            if node.type == "FUNCTION_DEFINITION":
                tmpvars = self._copy_status(thread_status)
                # reset the global variables
                basevars = tmpvars["variables"][-1]
                for variable in basevars:
                    if not basevars[variable]["global"]:
                        continue
                    if basevars[variable]["not_null"]:
                        basevars[variable]["value"] = crust.VALUE_NOT_NULL
                    else:
                        basevars[variable]["value"] = crust.VALUE_NOT_NULL_OR_NULL
                    basevars[variable]["uid"] = self.id_counter
                    self.id_counter += 1
                # insert the function arguments as variables
                tmpvars["variables"].insert(0, {})
                tmpvars["current_function"] = node.name
                if node.function_params is not None:
                    for parameter in node.function_params:
                        if parameter.t_void and (parameter.pointer == 0) and not parameter.function:
                            continue
                        params = self._get_variable_properties(thread_status, parameter, parameter.pointer, True)
                        if params["not_null"]:
                            params["value"] = crust.VALUE_NOT_NULL
                        else:
                            params["value"] = crust.VALUE_NOT_NULL_OR_NULL
                        params["uid"] = self.id_counter
                        self.id_counter += 1
                        params["init_line"] = parameter.line
                        params["enum"] = False
                        tmpvars["variables"][0][parameter.name] = params
                parameters = self._find_function(thread_status, node.name, -1)
                if parameters[0]["void"] and parameters[0]["pointer"] == 0:
                    tmpvars["return_is_crust"] = None
                else:
                    tmpvars["return_is_crust"] = self._check_statement_is_crust(node, parameters[0])
                    tmpvars["return_is_borrowed"] = parameters[0]["borrowed"]
                    tmpvars["return_cant_null"] = parameters[0]["not_null"]
                if self.verbose:
                    blank_spc = (last_len - len(node.name)) if (len(node.name) < last_len) else 0
                    msg = ("Processing {:s}" + " " * blank_spc + "\r").format(node.name)
                    print(msg, end = "")
                    last_len = len(node.name)
                    total_len = len(msg) - 1
                self._process_block(node.name, node.child1, tmpvars)
        if self.verbose:
            print(" " * total_len + "\r", end = "")


    def _process_block(self, function_name, tree, thread_status):

Sergio Costas's avatar
Sergio Costas committed
807 808
        """ Processes a single function """

809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984
        # if the function is not in the current file, don't analyze it
        if (os.path.abspath(tree[0].filename) != self.filename):
            return

        thread_status["debug_level"] = 0
        thread_status["loop_level"] = 0
        thread_status["find_label"] = None
        # delete all the old expoints, because each function is processed independently
        self.expoints = {}
        if tree[0].type == "START_BLOCK":
            tree = tree[1:] # the variable block has been already added
        blocks = [ (tree, thread_status) ]
        # for element in blocks[0][0]:
        #     print("Tipo: {:s}; Valor {:s}; Linea {:d}".format(str(element.type), str(element.intval), element.line))
        while len(blocks) > 0:
            current_block = blocks[0]
            blocks = blocks[1:]
            if current_block[0] is not None:
                retval = self._process_block2(current_block[0], current_block[1])
                if retval is not None:
                    blocks = retval + blocks
            else:
                if (current_block[1]["returned_something"] is None) and (current_block[1]["return_is_crust"] is not None):
                    self._add_error(current_block[1], self.MSG_ERROR, "Function '{:s}' expects a return value, but the code exited without it", function_name)


    def _process_block2(self, tree, thread_status):

        node = tree[0]
        #print("Tipo: {:s}; Valor {:s}; Linea {:d}".format(str(node.type), str(node.intval), node.line))
        #print("Tipo: {:s}; Nombre: {:s}; Linea: {:s}".format(str(node.type), str(node.name), str(node.line)))
        if (node.type == "TYPE_SPECIFIER"):
            if (node.enum_data is not None):
                self._process_enum(node, thread_status)
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "CRUST_DEBUG":
            if thread_status["find_label"] is None:
                self._do_debug(thread_status)
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "CRUST_ENABLE":
            if thread_status["debug_level"] > 0:
                thread_status["debug_level"] -= 1
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "CRUST_DISABLE":
            thread_status["debug_level"] += 1
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "CRUST_FULL_ENABLE":
            thread_status["debug_level"] = 0
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "START_BLOCK":
            # append a new block for the variables created inside this block
            thread_status["variables"].insert(0, {})
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "END_BLOCK":
            # check if there are blocks in use at the end of the block
            self._check_blocks_in_use(thread_status, thread_status["variables"][0], node.line)
            # remove the last block of variables
            thread_status["variables"] = thread_status["variables"][1:]
            if (len(thread_status["variables"]) == 1):
                # if we are at the end of the function, check the global variables
                self._check_global_vars(thread_status, node.line)
            else:
                if self._compare_status(node.uid, thread_status, self.expoints):
                    return None
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "EMPTY_DECLARATOR":
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "FUNCTION_DECLARATION":
            if not node.t_typedef:
                # it is a function pointer
                tmpparams = self._get_variable_properties(thread_status, None, node.pointer, False)
                tmpparams["function"] = True
                tmpparams["value"] = crust.VALUE_FUNCTION
                thread_status["variables"][0][node.name] = tmpparams
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "VARIABLE_DEFINITION":
            if (node.enum_data is not None):
                self._process_enum(node, thread_status)
            if node.t_typedef:
                return self._check_return_block(tree[1:], thread_status)
            var_defined = self._get_variable_properties(thread_status, node, node.pointer, False)
            if node.arrays is None:
                if node.t_static:
                    var_defined["value"] = crust.VALUE_NOT_NULL_OR_NULL
                else:
                    var_defined["value"] = crust.VALUE_UNINITIALIZED
            else:
                var_defined["value"] = crust.VALUE_NOT_NULL
            var_defined["init_line"] = None
            var_defined["enum"] = False
            #var_defined = self._node_fill_parameters(thread_status, node, var_defined)
            thread_status["variables"][0][node.name] = var_defined
            if (node.assignment is None) or (node.t_static) or (node.arrays is not None):
                return self._check_return_block(tree[1:], thread_status)
            # if the variable definition also has an assignment, then create a fake '=' node
            tmpnode = node.copy()
            tmpnode.type = "="
            tmpnode.child1 = [node.copy()]
            tmpnode.child1[0].type = "IDENTIFIER"
            tmpnode.child2 = [node.assignment[0].copy()]
            node = tmpnode
            # this fake node will be processed by the next 'if'

        if node.type == "IF":
            block_true, block_other = self._get_next_block(tree[1:])
            if block_other[0].type == "ELSE":
                block_false, block_other = self._get_next_block(block_other[1:])
                block_true += block_other
                block_false += block_other
            else:
                block_true += block_other
                block_false = block_other
            options = self._eval_statement(node.condition[0], thread_status, False, True)
            retvals = []
            status_true = {}
            status_false = {}
            for option in options:
                if option["condition"] == self.CONDITION_FALSE_TRUE:
                    if not self._compare_status(node.uid, option["thread_status"], status_true):
                        retvals += self._check_return_block(block_true, self._copy_status(option["thread_status"]))
                    if not self._compare_status(node.uid, option["thread_status"], status_false):
                        retvals += self._check_return_block(block_false, option["thread_status"])
                elif option["condition"] == self.CONDITION_TRUE:
                    if not self._compare_status(node.uid, option["thread_status"], status_true):
                        retvals += self._check_return_block(block_true, option["thread_status"])
                else: # option["condition"] == self.CONDITION_FALSE:
                    if not self._compare_status(node.uid, option["thread_status"], status_false):
                        retvals += self._check_return_block(block_false, option["thread_status"])
            return retvals

        if thread_status["find_label"] is None:
            if node.type == "RETURN":
                if node.child1 is not None:
                    thread_status["returned_something"] = True
                    threads = self._eval_statement(node.child1[0], thread_status, False)
                else:
                    thread_status["returned_something"] = False
                    threads = [ {"thread_status":thread_status, "value":self.VALUE_NOT_NULL_OR_NULL, "type":self.TYPE_NO_MATTER, "condition":self.CONDITION_FALSE_TRUE, "node":None} ]
                # check if there are blocks in use
                retval = []
                for thread in threads:
                    tmp_status = thread["thread_status"]
                    if tmp_status["return_cant_null"] and ((thread["value"] == self.VALUE_NULL) or (thread["value"] == self.VALUE_NOT_NULL_OR_NULL)):
                        self._add_error(tmp_status, self.MSG_ERROR, "Return value at line {:d} is NULL, but the function must not return a NULL value", node.line)
                    if (thread["node"] is not None):
                        var_data = self._find_variable(tmp_status, thread["node"].name, node.line)
                        if var_data["value"] == self.VALUE_UNINITIALIZED:
                            self._add_error(tmp_status, self.MSG_ERROR, "Returning variable '{:s}' at line {:d} is uninitialized",thread["node"].name, node.line)
                        if (var_data["value"] == self.VALUE_FREED) or (var_data["value"] == self.VALUE_FREED_OR_NULL):
                            self._add_error(tmp_status, self.MSG_ERROR, "Returning variable '{:s}' at line {:d} was freed at line {:d}",thread["node"].name, node.line, var_data["init_line"])
                        if (self._check_statement_is_crust(thread["node"], var_data)) and (thread["type"] == self.TYPE_CRUST) and (not var_data["borrowed"]):
                            self._free_block(tmp_status, thread["node"].name, node.line)
                    while(len(tmp_status["variables"]) > 1):
                        self._check_blocks_in_use(tmp_status, tmp_status["variables"][0], node.line)
                        tmp_status["variables"] = tmp_status["variables"][1:]
                    self._check_global_vars(thread_status, node.line)

                    if tmp_status["returned_something"] == True:
                        if (thread["type"] == self.TYPE_CRUST) and (tmp_status["return_is_crust"] == False):
                            self._add_error(tmp_status, self.MSG_ERROR, "Return statement at line {:d} is returning a crust value, but is must return a non-crust one", node.line)
                        if (thread["type"] != self.TYPE_CRUST) and (tmp_status["return_is_crust"] == True) and (thread["value"] != self.VALUE_NULL):
                            self._add_error(tmp_status, self.MSG_ERROR, "Return statement at line {:d} is returning a non-crust value, but is must return a crust one", node.line)
                        if tmp_status["return_is_crust"] is None:
                            self._add_error(tmp_status, self.MSG_ERROR, "Return statement at line {:d} is returning a value, but the function '{:s}' does not return a value", node.line, tmp_status["current_function"])
                        if thread["node"] is None:
                            return_is_borrowed = False
                        else:
985
                            return_is_borrowed = var_data["borrowed"]
986
                        if tmp_status["return_is_borrowed"] and (not return_is_borrowed) and (not node.child1[0].t_void):
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132
                            self._add_error(tmp_status, self.MSG_ERROR, "Function '{:s}' expects a borrowed value to return, but a non-borrowed was used at line {:d}", tmp_status["current_function"], node.line)
                        if (not tmp_status["return_is_borrowed"]) and return_is_borrowed:
                            self._add_error(tmp_status, self.MSG_ERROR, "Function '{:s}' expects a non-borrowed value to return, but a borrowed was used at line {:d}", tmp_status["current_function"], node.line)
                    else:
                        if tmp_status["return_is_crust"] is not None:
                            self._add_error(tmp_status, self.MSG_ERROR, "Function '{:s}' expects a return value, but the code exited at line {:d} without it", tmp_status["current_function"], node.line)
                    retval.append( (None, tmp_status) )
                return retval

            if node.type == "BREAK":
                if thread_status["loop_level"] == 0:
                    self._add_error(thread_status, self.MSG_CRITICAL, "Break not in a loop at line {:d}", node.line)
                    return [(None, thread_status)]
                while(tree[0].type != "END_LOOP"):
                    tree = tree[1:]
                return self._check_return_block(tree, thread_status)

            if node.type == "CONTINUE":
                if thread_status["loop_level"] == 0:
                    self._add_error(thread_status, self.MSG_CRITICAL, "Continue not in a loop at line {:d}", node.line)
                    return [(None, thread_status)]
                while(tree[0].type != "TMP_END_LOOP"):
                    tree = tree[1:]
                return self._check_return_block(tree, thread_status)

        if node.type == "END_LOOP":
            thread_status["loop_level"] -= 1
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "TMP_END_LOOP":
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "WHILE":
            thread_status["loop_level"] += 1
            condition = node.condition[0]
            wblock, other_block = self._get_next_block(tree[1:])
            retvals = []
            if not node.t_crust_no_zero:
                # loop executed zero times
                retvals += self._check_return_block([self._new_eval_false(condition)] + other_block[:], self._copy_status(thread_status) )
            # loop executed one time
            retvals += self._check_return_block([self._new_start_block(), self._new_eval_true(condition)] + wblock[1:-1] + [self._new_eval_false(condition), self._new_tmp_end_loop(), self._new_end_loop(), self._new_end_block()] + other_block[:], self._copy_status(thread_status))
            # loop executed two times
            retvals += self._check_return_block([self._new_start_block(), self._new_eval_true(condition)] + wblock[1:-1] + [self._new_eval_true(condition), self._new_tmp_end_loop()] + wblock[1:-1] + [self._new_eval_false(condition), self._new_tmp_end_loop(), self._new_end_loop(), self._new_end_block()] + other_block[:], thread_status)
            return retvals

        if node.type == "DO":
            thread_status["loop_level"] += 1
            condition = node.condition[0]
            wblock, other_block = self._get_next_block(tree[1:])
            retvals = []
            # loop executed one time
            retvals += self._check_return_block([self._new_start_block()] + wblock[1:-1] + [self._new_eval_false(condition), self._new_tmp_end_loop(), self._new_end_loop(), self._new_end_block()] + other_block[:], self._copy_status(thread_status))
            # loop executed two times
            retvals += self._check_return_block([self._new_start_block()] + wblock[1:-1] + [self._new_eval_true(condition), self._new_tmp_end_loop()] + wblock[1:-1] + [self._new_eval_false(condition), self._new_tmp_end_loop(), self._new_end_loop(), self._new_end_block()] + other_block[:], thread_status)
            return retvals

        if node.type == "FOR":
            thread_status["loop_level"] += 1
            if node.for_ch1 is None:
                init_loop = []
            else:
                init_loop = node.for_ch1
            if node.for_ch2 is None:
                condition = None
            else:
                condition = node.for_ch2[0]
                if (condition.type == "EMPTY_DECLARATOR"):
                    condition.type = "CONSTANT"
                    condition.intval = 1
                    condition.t_null = False
            if node.for_ch3 is None:
                end_loop = []
            else:
                end_loop = node.for_ch3
            wblock, other_block = self._get_next_block(tree[1:])
            retvals = []
            if not node.t_crust_no_zero:
                # loop executed zero times
                retvals += self._check_return_block([self._new_start_block()] + init_loop + [self._new_eval_false(condition), self._new_end_loop(), self._new_end_block()] + other_block[:], self._copy_status(thread_status))
            # loop executed one time
            retvals += self._check_return_block([self._new_start_block()] + init_loop + [self._new_eval_true(condition)] + wblock[1:-1] + end_loop + [self._new_eval_false(condition), self._new_tmp_end_loop(), self._new_end_loop(), self._new_end_block()] + other_block[:], self._copy_status(thread_status))
            # loop executed two times
            retvals += self._check_return_block([self._new_start_block()] + init_loop + [self._new_eval_true(condition)] + wblock[1:-1] + end_loop + [self._new_eval_true(condition), self._new_tmp_end_loop()] + wblock[1:-1] + end_loop + [self._new_eval_false(condition), self._new_tmp_end_loop(), self._new_end_loop(), self._new_end_block()] + other_block[:], thread_status)
            return retvals

        if node.type == "CASE":
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "DEFAULT":
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "SWITCH":
            thread_status["loop_level"] += 1
            first_part = node.condition[0]
            tree = tree[1:]
            switch_block, other_block = self._get_next_block(tree)
            blocks = {}
            default_block = None
            switch_block = switch_block[1:-1] # remove the START_BLOCK and the END_BLOCK
            while len(switch_block) > 0:
                if switch_block[0].type == "CASE":
                    blocks[switch_block[0]] = switch_block[1:]
                elif switch_block[0].type == "DEFAULT":
                    default_block = switch_block[1:]
                switch_block = switch_block[1:]
            threads = []
            block_zero = None
            block_no_zero = None
            for block in blocks:
                code = blocks[block][:]
                # create a comparison for equal
                condition_true = self.AST_node()
                condition_true.type = "EQ_OP"
                condition_true.line = -1
                condition_true.name = ""
                condition_true.child1 = [ first_part.copy() ]
                condition_true.child2 = [ block.condition[0].copy() ]
                if block.condition[0].t_null:
                    if block_zero is None:
                        block_zero = block.condition[0].copy()
                else:
                    if block_no_zero is None:
                        block_no_zero = block.condition[0].copy()
                # prepend the comparison evaluation before the code block
                code.insert(0, self._new_eval_true(condition_true))
                code.insert(0, self._new_start_block())
                code += [self._new_end_loop(), self._new_end_block()] + other_block[:]
                threads += self._check_return_block(code, self._copy_status(thread_status))
            if default_block is not None:
                # create a comparison for not equal for the default part
                if (block_zero is not None) or (block_no_zero is not None):
                    condition_false = self.AST_node()
                    condition_false.type = "NE_OP"
                    condition_false.line = -1
                    condition_false.name = ""
                    condition_false.child1 = [ first_part.copy() ]
                    if block_zero is not None:
                        condition_false.child2 = [ block_zero ]
                    else:
                        condition_false.child2 = [ block_no_zero ]
                    default_block.insert(0, self._new_eval_false(condition_false))
                default_block.insert(0, self._new_start_block())
                default_block += [self._new_end_loop(), self._new_end_block()] + other_block[:]
                threads += self._check_return_block(default_block, self._copy_status(thread_status))
            else:
1133 1134 1135
                threads += self._check_return_block(other_block, self._copy_status(thread_status))
            return threads

1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187

        if (node.type == "EVAL_TRUE") or (node.type == "EVAL_FALSE"):
            threads = self._eval_statement(node.condition, self._copy_status(thread_status), False, True)
            retvals = []
            for thread in threads:
                if (node.type == "EVAL_TRUE") and (thread["condition"] == self.CONDITION_FALSE):
                    continue
                if (node.type == "EVAL_FALSE") and (thread["condition"] == self.CONDITION_TRUE):
                    continue
                retvals += self._check_return_block(tree[1:], thread["thread_status"])
            if len(retvals) == 0:
                return None
            else:
                return retvals

        if node.type == "GOTO":
            if thread_status["find_label"] is None:
                thread_status["find_label"] = node.name
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "LABEL":
            if thread_status["find_label"] is not None:
                if node.name == thread_status["find_label"]:
                    thread_status["find_label"] = None
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "CRUST_SET_NULL":
            var_name = node.child1[0].name
            var_data = self._find_variable(thread_status, var_name, node.line, False, False)
            if var_data["global"]:
                if var_data["accessed"]:
                    self._add_error(thread_status, self.MSG_WARNING, "Setting to NULL the state of the already accessed global variable '{:s}' at line {:d}", var_name, node.line)
                self._set_var_value(thread_status, var_name, self.VALUE_NULL, node.line, True)
            else:
                self._add_error(thread_status, self.MSG_CRITICAL, "Trying to set to NULL the state of the non-global variable '{:s}' at line {:d}", var_name, node.line)
            return self._check_return_block(tree[1:], thread_status)

        if node.type == "CRUST_SET_NOT_NULL":
            var_name = node.child1[0].name
            var_data = self._find_variable(thread_status, var_name, node.line, False, False)
            if var_data["global"]:
                if var_data["accessed"]:
                    self._add_error(thread_status, self.MSG_WARNING, "Setting to NOT_NULL the state of the already accessed global variable '{:s}' at line {:d}", var_name, node.line)
                self._set_var_value(thread_status, var_name, self.VALUE_NOT_NULL, node.line, True)
            else:
                self._add_error(thread_status, self.MSG_CRITICAL, "Trying to set to NOT_NULL the state of the non-global variable '{:s}' at line {:d}", var_name, node.line)
            return self._check_return_block(tree[1:], thread_status)
        threads = self._eval_statement(node, thread_status, True)
        retvals = []
        for thread in threads:
            retvals += self._check_return_block(tree[1:], thread["thread_status"])
        return retvals