ec: common: Make IS_ENABLED fail on unknown values

If IS_ENABLED is called with any unknown values, a compiler error will be
thrown. This change requires that the optimizer always be enabled,
otherwise errors will be thrown when a value is not defined.

BUG=none
BRANCH=none
TEST=make runtests TEST_LIST_HOST="is_enabled_error is_enabled"

Change-Id: I1b166311f81d07e48b3665f4bc0e9502d2ccc4c6
Signed-off-by: Raul E Rangel <rrangel@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1592728
Reviewed-by: Jett Rink <jettrink@chromium.org>
Reviewed-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-by: Daisuke Nojiri <dnojiri@chromium.org>
This commit is contained in:
Raul E Rangel 2019-05-03 12:48:41 -06:00 committed by chrome-bot
parent 1598a615eb
commit aaba1d5efd
10 changed files with 175 additions and 19 deletions

View File

@ -308,8 +308,9 @@ rw-objs := $(sort $(rw-common-objs) $(rw-only-objs))
ifeq ($(CONFIG_SHAREDLIB),y)
ro-objs := $(filter-out %_sharedlib.o, $(ro-objs))
endif
ro-deps := $(ro-objs:%.o=%.o.d)
rw-deps := $(rw-objs:%.o=%.o.d)
ro-deps := $(addsuffix .d, $(ro-objs))
rw-deps := $(addsuffix .d, $(rw-objs))
deps := $(ro-deps) $(rw-deps) $(deps-y)
.PHONY: ro rw

View File

@ -94,7 +94,8 @@ cmd_cxx_to_host = $(HOSTCXX) -std=c++0x $(COMMON_WARN) $(HOST_CXXFLAGS)\
cmd_o_to_a = $(AR) rcs $@ $^
cmd_host_test = $(MAKE) --no-print-directory BOARD=host PROJECT=$* \
V=$(V) out=build/host/$* TEST_BUILD=y EMU_BUILD=y CROSS_COMPILE= \
$(TEST_FLAG) build/host/$*/$*.exe
$(if $(TEST_SCRIPT),TEST_SCRIPT=$(TEST_SCRIPT)) $(TEST_FLAG) \
build/host/$*/$*.exe
cmd_run_host_test = ./util/run_host_test $* $(silent)
# generate new version.h, compare if it changed and replace if so
cmd_version = ./util/getversion.sh > $@.tmp && \
@ -122,6 +123,7 @@ cmd_emmc_bootblock = $(out)/util/gen_emmc_transfer_data \
$(if $(BOOTBLOCK),-i $(BOOTBLOCK)) -o $@
cmd_ipi_table = $(out)/util/gen_ipi_table $@
cmd_cp_script = cp "$<" "$@" && chmod +x "$@"
# commands for RSA signature: rwsig does not need to sign the whole image
# (it signs the RW part separately). usbpd1 type needs to sign the final image.
@ -471,11 +473,16 @@ $(out)/%.smap: $(out)/%.elf
ifeq ($(TEST_FUZZ),y)
$(out)/$(PROJECT).exe: $(rw-only-objs) $(out)/libec.a
$(call quiet,fuzz_exe,EXE )
else ifneq ($(TEST_SCRIPT),)
$(out)/$(PROJECT).exe: test/$(TEST_SCRIPT) | $(ro-objs)
$(call quiet,cp_script,CP )
else
$(out)/$(PROJECT).exe: $(ro-objs)
$(call quiet,exe,EXE )
endif
$(out)/RO/%.o.cmd:%.c
$(file > $@,$(subst .o.cmd,.o,$(cmd_c_to_o)))
$(out)/RO/%.o:%.c
$(call quiet,c_to_o,CC )
$(out)/RW/%.o:%.c

View File

@ -92,6 +92,8 @@ HOST_CPPFLAGS=$(CFLAGS_DEFINE) $(CFLAGS_INCLUDE) $(CFLAGS_TEST) \
ifneq ($(BOARD),host)
CPPFLAGS+=-ffreestanding -fno-builtin -nostdinc -nostdlib
CPPFLAGS+=-Ibuiltin/
else
CPPFLAGS+=-Og
endif
CFLAGS=$(CPPFLAGS) $(CFLAGS_CPU) $(CFLAGS_DEBUG) $(COMMON_WARN) $(CFLAGS_y)
CFLAGS+= -ffunction-sections -fshort-wchar

View File

@ -236,29 +236,49 @@ enum ec_error_list {
/*
* Getting something that works in C and CPP for an arg that may or may
* not be defined is tricky. Here, if we have "#define CONFIG_BOOGER"
* we match on the placeholder define, insert the "0," for arg1 and generate
* the triplet (0, 1, 0). Then the last step cherry picks the 2nd arg (a one).
* When CONFIG_BOOGER is not defined, we generate a (... 1, 0) pair, and when
* the last step cherry picks the 2nd arg, we get a zero.
* not be defined is tricky. Here, if we have "#define CONFIG_FOO"
* we match on the placeholder define, insert the "_, 1," for arg1 and generate
* the triplet (_, 1, _, (...)). Then the last step cherry picks the 2nd arg
* (a one).
* When CONFIG_FOO is not defined, we generate a (_, (...)) pair, and when
* the last step cherry picks the 2nd arg, we get a code block that verifies
* the value of the option. Since the preprocessor won't replace an unknown
* token, we compare the option name with the value string. If they are
* identical we assume that the value was undefined and return 0. If the value
* happens to be anything else we call an undefined method that will raise
* a compiler error. This technique requires that the optimizer be enabled so it
* can remove the undefined function call.
*
*/
#define __ARG_PLACEHOLDER_ 0,
#define config_enabled(cfg) _config_enabled(cfg)
#define _config_enabled(value) __config_enabled(__ARG_PLACEHOLDER_##value)
#define __config_enabled(arg1_or_junk) ___config_enabled(arg1_or_junk 1, 0, 0)
#define __ARG_PLACEHOLDER_ _, 1,
#define _config_enabled(cfg, value) \
__config_enabled(__ARG_PLACEHOLDER_##value, cfg, value)
#define __config_enabled(arg1_or_junk, cfg, value) ___config_enabled( \
arg1_or_junk _,\
({ \
int __undefined = __builtin_strcmp(cfg, #value) == 0; \
extern int IS_ENABLED_BAD_ARGS(void) __attribute__(( \
error(cfg " must be <blank>, or not defined.")));\
if (!__undefined) \
IS_ENABLED_BAD_ARGS(); \
0; \
}))
#define ___config_enabled(__ignored, val, ...) val
/**
* Checks if a config option is defined to an empty value.
* Checks if a config option is enabled or disabled
*
* IS_ENABLED(CONFIG_MY_OPTION) will return 1 in the following case:
* #define CONFIG_MY_OPTION
* Enabled examples:
* #define CONFIG_FOO
*
* Otherwise if the option has not been defined or defined with a value, it will
* return 0.
* Disabled examples:
* #undef CONFIG_FOO
*
* @param CONFIG_OPTION
* If the option is defined to any value a compiler error will be thrown.
*
* Note: This macro will only function inside a code block due to the way
* it checks for unknown values.
*/
#define IS_ENABLED(option) config_enabled(option)
#define IS_ENABLED(option) _config_enabled(#option, option)
#endif /* __CROS_EC_COMMON_H */

View File

@ -36,6 +36,8 @@ test-list-host += hooks
test-list-host += host_command
test-list-host += inductive_charging
test-list-host += interrupt
test-list-host += is_enabled
test-list-host += is_enabled_error
test-list-host += kb_8042
test-list-host += kb_mkbp
test-list-host += kb_scan
@ -101,6 +103,7 @@ host_command-y=host_command.o
inductive_charging-y=inductive_charging.o
interrupt-scale=10
interrupt-y=interrupt.o
is_enabled-y=is_enabled.o
kb_8042-y=kb_8042.o
kb_mkbp-y=kb_mkbp.o
kb_scan-y=kb_scan.o
@ -152,3 +155,6 @@ TPM2_ROOT := $(CROS_WORKON_SRCROOT)/src/third_party/tpm2
$(out)/RO/common/new_nvmem.o: CFLAGS += -I$(TPM2_ROOT)
$(out)/RO/test/nvmem.o: CFLAGS += -I$(TPM2_ROOT)
$(out)/RO/test/nvmem_tpm2_mock.o: CFLAGS += -I$(TPM2_ROOT)
host-is_enabled_error: TEST_SCRIPT=is_enabled_error.sh
is_enabled_error-y=is_enabled_error.o.cmd

35
test/is_enabled.c Normal file
View File

@ -0,0 +1,35 @@
/* Copyright 2019 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Test the IS_ENABLED macro.
*/
#include "common.h"
#include "test_util.h"
#undef CONFIG_UNDEFINED
#define CONFIG_BLANK
static int test_undef(void)
{
TEST_ASSERT(IS_ENABLED(CONFIG_UNDEFINED) == 0);
return EC_SUCCESS;
}
static int test_blank(void)
{
TEST_ASSERT(IS_ENABLED(CONFIG_BLANK) == 1);
return EC_SUCCESS;
}
void run_test(void)
{
test_reset();
RUN_TEST(test_undef);
RUN_TEST(test_blank);
test_print_result();
}

9
test/is_enabled.tasklist Normal file
View File

@ -0,0 +1,9 @@
/* Copyright (c) 2019 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**
* See CONFIG_TASK_LIST in config.h for details.
*/
#define CONFIG_TEST_TASK_LIST /* No test task */

27
test/is_enabled_error.c Normal file
View File

@ -0,0 +1,27 @@
/* Copyright 2019 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Test the IS_ENABLED macro fails on unexpected input.
*/
#include "common.h"
#include "test_util.h"
#define CONFIG_VALUE TEST_VALUE
static int test_invalid_value(void)
{
/* This will cause a compilation error */
TEST_ASSERT(IS_ENABLED(CONFIG_VALUE) == 0);
return EC_ERROR_UNKNOWN;
}
void run_test(void)
{
test_reset();
RUN_TEST(test_invalid_value);
test_print_result();
}

40
test/is_enabled_error.sh Normal file
View File

@ -0,0 +1,40 @@
#!/bin/bash -e
# Copyright 2019 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
TEST_DIR="$(dirname "${BASH_SOURCE[0]}")"
TEST_CMD="$(cat "${TEST_DIR}/RO/test/is_enabled_error.o.cmd")"
TEST_ERROR_COUNT=0
for test_value in 0 1 2 A "5 + 5"; do
echo -n "Running TEST_VALUE=${test_value}..."
TEST_CMD_COMPLETE="${TEST_CMD} \"-DTEST_VALUE=${test_value}\""
if BUILD_OUTPUT="$(sh -c "$TEST_CMD_COMPLETE" 2>&1)"; then
echo "Fail"
echo "Compilation should not have succeeded for" \
"TEST_VALUE=${test_value}"
echo "$BUILD_OUTPUT"
TEST_ERROR_COUNT=$((TEST_ERROR_COUNT+1))
continue
fi
EXPECTED_ERROR="CONFIG_VALUE must be <blank>, or not defined"
if grep -q "$EXPECTED_ERROR" <<< "$BUILD_OUTPUT"; then
echo "OK"
else
echo "Fail"
echo "Expected to find: $EXPECTED_ERROR"
echo "Actual error:"
echo "$BUILD_OUTPUT"
TEST_ERROR_COUNT=$((TEST_ERROR_COUNT+1))
fi
done
if [[ $TEST_ERROR_COUNT -eq 0 ]]; then
echo "Pass!"
else
echo "Fail! (${TEST_ERROR_COUNT} tests)"
fi

View File

@ -0,0 +1,9 @@
/* Copyright (c) 2019 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
/**
* See CONFIG_TASK_LIST in config.h for details.
*/
#define CONFIG_TEST_TASK_LIST /* No test task */